20220511数据结构绿书读书笔记






20220511
Life类的定义
每个Life对象都需要包含一个矩形数组,用1表示存活的细胞,0表示死亡的细胞。这样表示的好处是,如果要计算一个特定细胞的邻居,我们只需要把相邻的加起来就行了。因为每代的更新中都需要重复的计算每个细胞的邻居数量,所以Life类应当包含一个成员方法neighborCount来做这个任务。因为client的代码不需要知道这个方法,所以设置成private

计算邻居
可以预见(这说法特别像数学证明里的显然可得,然而初学者设计程序并不会有这种经验,只能是踩过坑才知道),如果需要边界细胞的邻居需要更多的判断条件,我们可以做一些优化,让数组的实际大小比定义的大一些,周围留出一圈,这样既避免了从0开始数的麻烦,也避免掉了多余的判断,让所有单元格计算邻居的时候可以用相同的代码

更新矩阵
更新的动作是相当明确的,我们首先计算一个新的矩阵用于记录更新后的数据,然后完整的把新的数据复制回原地即可(直接用新的不好吗?)
更新过程的算法比较容易,我们遍历非边界的地方,然后判断存活邻居的个数,实际上只需要判断2个和3个就行,其他的无论如何都是死亡。
这里需要注意下,如果需求让你打印死亡或存活的原因,就不能这么更新了。这也是很多搞算法的同学容易忽略的,算法题往往只需要一个正确的结果,记录这个结果一个bit位或者一个int就能搞定,而真实的需求往往会要求记录更多的信息。在这个例子里,可能的需求就是记录下每个细胞存活和死亡的代数和原因,后面可能还有可能查出相同复活和死亡原因的细胞分布,或者统计每个细胞存活的代数。什么?你没记?你怎么能不记呢?不记到时候加需求你怎么做啊?hhhhhh
程序的运行应当尽可能的留痕以供人工查阅或后期处理,记录尽可能原始的数据,而不是像AI神经网络一样输出一个莫名其妙大概率正确的结果而不知道为什么
如果考虑实际需求,那么数组的类型就不能是int了,还得再套个对象,记录当前细胞每代更新对自己的影响结果这种原始数据备用,可以用于计算它的出生代数、死亡代数、存活实际等其他数据,以防产品后面加需求。这是一个来自刚工作几年没少踩坑的开发的经验之谈,信不信由你

输入和输出
程序一般设计上是给很多人使用的,输入和输出的方法通常是最长的,输入的程序需要完整检查用户出入的正确性和一致性,输出要合理的格式化,要好好考虑什么应该输出什么不应该

编程原则:
保持你的输出和输出再不同的方法里,来让他们可以被轻易的修改,让你的系统有一定的custom tailored

提示语打印程序是一个非常简单的程序,只负责打印提示

测试用例drivers
可以写一小段程序给一个方法提供一些特定的输入,单独测试一个方法的运行是否正常

程序跟踪和debug
一个很常用的工具就是打印语句,详细的打印程序执行时所处的位置和相关变量的状态,生产环境一般用日志框架来处理,有的公司会有完整的日志收集、搜索、监控平台来做这件事情,自己写着玩直接print就好
脚手架代码也可以在debug的时候提供额外的帮助,用过之后记得删掉就好。当你的程序出现奇怪的错误时,脚手架代码可以放在1个或2个主程序的关键位置,用于快速确认问题出现的具体行数。

程序测试原则
测试数据的质量远比测试数据的数量要重要
程序测试可以证明bug的存在型,但无法证明bug不存在

程序测试方法
黑盒测试
使用普通数据、特殊的正常数据、边界附近的数据、非法的数据(参考测试工程师进酒吧)

白盒测试
分析程序的的每个分支,对每个执行路径都进行测试,大型程序种这种测试并不现实,一般选择性测试主要的执行路径

Ticking-Box测试
不知道怎么翻译,翻译成随缘测试吧hhh
写完了让用户自己去用吧,出了问题一定是你不会用,才不是bug呢

20220511结束,耗时一个半小时,明天继续看程序的评估


20220510数据结构绿书读书笔记






20220510
解决方案:类、对象、方法
大概来说,一个程序运行上述游戏需要以下几个部分组成
1.算法
设置一个初始布局状态,打印这个布局状态,当用户想看下一代的时候根据规则更新布局状态并打印当前状态

2.类和对象
一个configuration布局实体/实例/对象,类型是Life,我们让这个对象使用Life的3个方法:initialize将会初始化一个最开始的状态,print打印当前布局,update让布局更新为下一代

一个Clients类,用于访问前面的对象,可以声明前面这些类的实例。一般可以叫做Main活着xxxTest之类的都ok

3.规范
当编写一个客户端程序的时候,一个很重要的点是我们不需要知道数据是如何存储的,也不需要知道这些方法是如何被编程的。这是自顶向下设计非常重要的一个转变,写代码多了就会熟悉这种思路。这通常被称为information hiding
当我们实现Life类的时候,我们才需要决定如何存储这些数据,以及如何实现这些方法,这些数据和方法对Life来说都是私有的,client程序不需要知道这些,只需要访问公开的方法即可

编程中一个相当重要的道德行为就是命名规范,尽管数据和算法之前就存在了,但只有当给他们有意义的名字的时候,他们才能被正常的阅读。为了精确的理解每个变量和方法的作用,有时需要给类、变量、方法编写文档来说明。它们的名字应当简洁且清晰的精确表示他们的含义,寻找一个好的变量名并不总是一个简单的工作

使用小写字母l、大写字母O、数字0的时候需要额外的注意,底下是一个蛋疼的例子(吐槽下苹果上对于括号的渲染,也是个大坑)

注释要避免解释代码本身的动作,解释Why,而不是解释How

编程原则:

  • 让程序的阅读起来更容易,因为一个程序的阅读时间要远多于编写时间
  • 不要丢掉全局视野
  • 用类来对基础概念进行建模
  • 每个方法都应该只做一件事,但做到极致
  • 每个类都应该隐藏一些东西
  • 保持上下文简洁、如非必要,不要用全局变量
  • 尽可能的避免边际效应,如果一定要用全局变量,写文档

编码&测试&重构
stub打桩技术,快速让主程序通过编译或为了方便测试,可以先搭架子

20220510结束,耗时1小时10分钟
顺带一提,vscode Code-runner跑代码乱码可以这样解决


20220509数据结构绿书读书笔记

20220509
数据结构读书笔记
全书目录如下
1.编程原则
2.栈
3.队列
4.链栈&队列
5.回溯
6.列表和串
7.搜索
8.排序
9.表格与信息检索
10.二叉树
11.多叉树
12.图
13.波兰式

开坑!

第一章其实上学时候就看过,我很多的习惯都是因为这本书才养成的,但当时并没有记录- -,这次一并补上,看英文书的好处就是写的足够详细,什么时候读都能get到作者的意思,不好的地方就是太啰嗦了。但好在我是为了消磨时间才立的这个flag
编写一个large的程序往往有很多问题,这里的large可以理解为实现复杂或者规模庞大
问题1.精确的定义问题
拆分并逐渐分割成小问题,直到可以方便处理
问题2.程序设计
“先跑起来再慢慢优化”对小程序也许有用,但并不适合large的程序,大型程序的每个部分都需要良好的组织、清晰的实现、良好的可读性,否则它将难以复用或被他人阅读
问题3.选择合适的数据结构
同一个问题多种算法和数据结构都能解决,很难选择出一个最好的。在算法设计上一般来说选择的最好方法是看数据是如何存储的:
1.数据之间的关系是怎样的
2.那些数据保持在内存中
3.当需要的时候哪些数据已经被计算好了
4.哪些数据是存在文件里的?文件是如何被组织的
后面会先学列表、栈、队列,然后再学几个厉害的算法来对数据做处理,比如排序和搜索
问题4.算法分析
当有多种方法管理数据和设计算法的时候,就必须有一个标准来帮忙评判算法。
问题5.测试和验证程序的正确性
debug程序的难度会随着程序的规模而增加,可能是指数级
正确性:减少错误,让程序更加方便维护、方便我们验证算法是正确的、有方法可以测试我们的程序,让我们自信的说程序没有非预期行为

问题6.维护
当程序开始提供服务后,可能还会有新的需求

吹一波C++NB:
C++允许抽象数据、允许面向对象设计、允许自顶向下的设计方法、允许代码重用 。。。。。

casestudy GameOfLife
cell不知道是翻译作细胞更好,还是叫单元格更好,反正前面都说grid的了,我就先当成细胞了
生存模拟,在一个无边界的矩阵中每个细胞都可以被有机物占用或不占用,被有机物占用的被称作“活着”,未被占用的称作“死的”
规则如下
1.给定1个细胞,它相邻的8个细胞都可以被接触,横竖斜都可以,称之为邻居
2.如果一个细胞活着,但同时邻居都没有存活,或仅剩1个存活,则在下一代将因孤独而死
3.如果一个细胞活着,但同时有4个或4个以上的邻居存活,则在下一代将因拥挤而死
4.如果一个存活的细胞有2个或3个邻居存活,则在下一代可以继续保持存活
5.如果一个细胞死了,但如果正好有3个活着的邻居,那么在下一代将被复活。所有死亡的其他细胞在下一代将保持死亡状态(这不是废话么hhh)
6.所有的生死均在一瞬间同时完成,所以正在死亡的细胞可以帮助其他细胞复活,但无法阻止其他细胞因拥挤导致的死亡。刚出生的细胞也无法复获或杀死上一代存活的细胞

一组特定排列的细胞叫做初始布局,上面的规则描述了一个布局如何在每代更替中变为下一个布局

多样性;有的布局会很快死干净、有的会从很小一点涨到巨大。。。难以人工预测
小目标:写一个程序模拟一个初始布局是如何一代代变化的
这个游戏被发明后不久,MARTIN GARDNER就在Scientific American的专栏里讨论过,被很多人所吸引

跳过面向对象基础部分,直接进入正题

本书的后续将严格区分method和function,中文翻译为方法和函数,方法是公开的,函数是私有的。(我写笔记会尽量遵守,但不保证hhhh

虽然书用的是C++,但并不影响我使用Java来表达相同的内容,代码将于我觉得可以的时机,公开在github和gitee上
耗时一个半小时,才读了8页hhhh,能读得完么= = 后面少记点笔记也许能读的快点
20220509结束

20220424树的布局算法

某群友需要绘制树形结构数据到excel中,拍脑袋想了下感觉不是很难,遂给出算法分析。

偷懒用了excel的column和row函数获取所在行列,直接能看到结果,目测也是算得出来结果的

因为要求是横着的,所以例子是横着的。但算法定义时候是竖着思考的,所以用的是宽度w和距离左侧的宽度l,算完了再横过来画就完了

首先这样定义子节点宽度:
当不存在子节点(即叶子节点)时,宽度为1
当存在子节点时,如果子节点为奇数n则宽度为n,如果为偶数n则宽度为n+1。说明:因为偶数的情况下父节点需要居中占一个,(n+1)/2的位置要空着给父节点
然后计算节点距离左侧长度l:取已知的层序i
如果是非叶子节点:取上一步宽度w, l=(w-1)/2,如果i>1再加上前面所有节点计算的宽度,如果当前层是偶数个子树则再+1
如果是叶子节点,取所在子树上一步计算的宽度,直接铺开排列。非首个子树加下前面的所有宽度,注意偶数子树情况下中间空1个。

偷懒没实现,谁有兴趣可以写下代码试试,允许使用任意数据结构存储相关信息

 

有个好的文章参考

https://llimllib.github.io/pymag-trees/

20211023.Web后端参数检查的通用代码生成设计与实现

Web后端参数检查的通用代码生成设计与实现

前言

本文章来自这里,主要讨论&设计常见接口实现逻辑中的参数校验部分,主要是实现到Controller实现逻辑的时候决定修改总计划,不再追求全流程代码生成,原因不再赘述。

相关源代码库

元模型定义 https://github.com/956237586/hyldesigner

代码生成器  https://github.com/956237586/hyldesigner-codegen

realworld-mdd demo https://github.com/956237586/realworld-mdd

需求价值

简化&替代实际业务场景下常见的参数校验代码编写,提高开发效率,减少浪费在Copy And Paste上的宝贵生命

目标

实现参数校验部分的meta-model&对应的代码生成器

需要支持自动生成常见的校验策略,包括字符串trim后非空、字符串长度、字符串分中英文字符长度限制、数字范围、数据库存在性校验、多字段直接人工定义的约束校验等

校验策略支持2种,打断or完整校验

校验结果分为校验成功和校验失败

支持选择是否生成校验上下文记录额外IO的结果

其中完整策略下校验失败时支持生成详细的校验结果用于程序判断原因

支持校验结果自定义处理,打断策略下默认抛一个异常让开发者自己处理

支持的组合如下

1.打断策略

1.1.校验成功

1.1.1.不生成上下文

返回值void,结束方法运行

1.1.2.生成上下文

返回值中包含所有额外IO的原始结果变量

1.2.校验失败

打断策略下遇到任何一个校验错误都会直接停止后续校验,抛出异常和错误信息,这个错误信息给人看的,不支持程序来判断原因。

1.2.1.不生成上下文

异常中不包含上下文信息

1.2.2.生成上下文

异常中包含所有额外IO的原始结果变量

2.完整策略

2.1.校验成功

2.1.1.不生成上下文

返回值void,结束方法运行

2.1.2.生成上下文

返回值中包含所有额外IO的原始结果变量

2.2.校验失败

2.2.1.不生成上下文

异常中不包含上下文信息

2.2.2.生成上下文

异常中包含所有额外IO的原始结果变量

2.2.3.不生成详细的校验结果

异常中不包含详细错误信息

2.2.4.生成详细的校验结果

异常中需要包含校验失败的字段以及错误信息

 

需求分析

spring内置了@Valid注解来完成单一对象下单独属性的检查,且支持扩展自定义逻辑。看上去很好,很强大。

虽然但是,实际如果你用了,就会发现在真实业务流程下,这种校验有多鸡肋。

spring理解的校验:通过代码或注解定义静态规则,且可以让开发者拿到过不去规则的原因

简单的校验大概都是getXXX  然后做基础的校验,这部分基础校验就是spring理解的校验,如果失败打断流程,成功要把继续往下执行。

复杂校验往往依赖前面校验的结果,或依赖其他字段的状态,或依赖数据库来做校验。比如queryDbByXXX  成功则取部分内容继续流程,失败则打断。

根据工作经验来看,实际业务的校验往往不是单纯的单一规则校验就能完成,同时为了性能考虑校验过程中查到的数据库的结果还需要给下一步操作来使用。

这时候会面临方案选择:1.简单校验使用spring注解,复杂校验手动编写逻辑。2.全部手动编写逻辑

如果是写新需求:

方案1和2往往都是找个相同类型已有的类似代码,改字段、注解、属性名。这个过程将会占用大量时间在Copy And Paste,且毫无编程体验,可以说真的是板砖,并且还是从乱七八糟的地方搬

如果是维护改造类任务:

无论方案1还是2你会发现校验逻辑会比较分散,可能出现在类的属性注解、Controller的参数注解、各个Service的不知道第几层嵌套里。如果有人还用了反射来实现,那更是轻易找不全逻辑。

这时候如果想服用已有代码的部分业务流程,你就得小心的从原有屎山代码中拆分校验逻辑和业务逻辑。

虽然方案1可以通过ThreadLocal或缓存等手段来跨方法传递保存Spring校验过程中产出的上下文信息(比如从数据库查出的对象),但这样做无形中增加了代码后续的维护成本和理解难度。后续维护麻烦不说且容易改丢东西。所以实际开发中一般还是要手动写代码实现而不是仅简单的使用spring注解。

因此设想一种方案3,在代码之外尽可能简单的定义这些约束条件,通过代码生成器自动生成指定类实例(下文可能称之为对象,一个意思)的校验代码逻辑

概要设计

说了这么多其实可以类比数据关系约束对这完整性的描述(可能不够严谨,但意思大概对):

1.实体完整性

对象中的每个字段,是否都满足自身的约束,如字符非空/字符长度/数字值范围/字符串枚举等

这些校验实现起来相对简单,无需额外IO,校验所依赖的数据输入仅是字段的值本身,按每个规则直接实现即可。如果有多个规则,则同时执行多条校验即可

2.参照完整性

情况1 对象中的每个字段如果是另一个对象的主属性

情况2 另一个对象通过引用形式嵌入到了当前对象中

对于情况1  这种校验往往仅靠CPU无法完成,需要额外IO操作,比如查mysql数据库、redis。额外IO操作的结果后续可能有用也可能没用,如果校验通过了,可能就要继续用,否则大概率是没用的

对于情况2 和关系型数据库定义不一样的是,当对象的字段是引用且必填时候,需要额外校验引用非空,同时递归校验该引用对象的完整性

3.用户定义完整性

除了1,2意外的规则都可以纳入用户定义完整性

例如:字段间的特殊关联约束(字段1=A时字段2必须=BCD)

这部分校验和业务逻辑会有部分重合,需要根据情况来慎重决定是应该算作校验还是业务逻辑,必要时需要慎重选择

个人建议当这个规则后续大概率在业务中永远需要校验时才算业务逻辑,否则均按校验逻辑处理,避免污染核心业务逻辑。

例如用户A仅能修改uid=A的数据行:在不需要管理后的时候,算业务逻辑。当管理后台有超管可随意修改时,算校验逻辑。

详细设计

校验流程依赖元模型提供校验规则信息,因此需要进行元模型设计

校验流程分为主流程方法签名、主流程实现、子流程方法签名、子流程实现四部分。主流程由多个子流程实现,子流程由多个子代码模板组合而成。

1.元模型设计

已有定义:请求参数(Payload)包含多个属性(Attr),每个属性类型可能是字符串(Str)、整数(Int)、长整数(Long)、引用类型(ReferencedType)

1.1.类抽象结构设计

abstract Eclass AbstractCheckRule{// 约束的抽象父类  使用EReference作为Payload和Attr的子节点

EString desc

}

abstract Eclass DomainEntityCheckRule extends AbstractCheckRule 实体完整性

Eclass ReferencedCheckRule extends AbstractCheckRule  参照完整性 {

EReference Dto;

}

abstract Eclass UserDefinedCheckRule extends AbstractCheckRule 用户定义完整性

Eclass CustomCheckRule extends UserDefinedCheckRule 留个自定义的类方便扩展任意校验操作

逻辑上第一种DomainEntityCheckRule和第二种ReferencedCheckRule 字段校验的规则附加在属性(Attr)上,第三种校验附加在Payload上

1.Payload的Attr来自于DomainEntity的Attr映射:优先使用DomainEntity的AbstractCheckRule 子节点,同名的checkRule覆盖DomainEntity的(表达式可能不好写,先不实现)

2.Payload的Attr不是来自于DomainEntity的Attr映射:使用自己的AbstractCheckRule 子节点

3.Payload来自于DomainEntity映射:优先使用DomainEntity的AbstractCheckRule 子节点,同名的checkRule覆盖DomainEntity的(表达式可能不好写,先不实现)

4.Payload不是来自于DomainEntity映射:使用自己的AbstractCheckRule 子节点

对有映射关系的Payload或Attr来说,暂时考虑实现为合并DomainEntity的AbstractCheckRule,同时Payload增加CheckRuleMixIn属性支持合并其他Payload的AbstractCheckRule(参考Python的MixIn设计模式可以灵活增加通用的规则,之所以不用继承是因为避免模型管理的困难,不便于后期搜索功能的实现)

1.2.常用校验规则设计

目前的数据类型分为基本数据类型PrimitiveType和引用类型ReferencedType

基本类型包括字符串str,整型int,布尔类型bool, 长整型long

引用目前类型包括Dto,RequestPayload,ResponseResult

继续声明常用具体的检测逻辑

abstract Eclass PrimitiveTypeCheckRule extends DomainEntityCheckRule

abstract Eclass ReferencedTypeCheckRule extends ReferencedCheckRule

Eclass MustNotNullCheckRule extends PrimitiveTypeCheckRule, ReferencedTypeCheckRule

Eclass MustNullCheckRule extends PrimitiveTypeCheckRule, ReferencedTypeCheckRule

1.2.1.字符串str

abstract Eclass StringTypeCheckRule extends PrimitiveTypeCheckRule

Eclass NotEmptyCheckRule extends StringTypeCheckRule

Eclass LengthCheckRule extends StringTypeCheckRule {

EInteger min

EInteger max

}

Eclass RegexCheckRule extends StringTypeCheckRule {

EString regex

}

1.2.2.数字int long

abstract Eclass NumberTypeCheckRule extends PrimitiveTypeCheckRule

abstract Eclass IntegerTypeCheckRule extends NumberTypeCheckRule

abstract Eclass LongTypeCheckRule extends NumberTypeCheckRule

Eclass IntegerRangeCheckRule extends IntegerTypeCheckRule {

EInteger min

EInteger max

}

Eclass LongRangeCheckRule extends LongTypeCheckRule{

ELong min

ELong max

}

1.2.3.布尔 bool

abstract Eclass BoolTypeCheckRule extends PrimitiveTypeCheckRule

Eclass TrueValueCheckRule extends BoolTypeCheckRule

Eclass FalseValueCheckRule extends BoolTypeCheckRule

1.2.4.引用类型

abstract Eclass DtoTypeCheckRule extends ReferencedTypeCheckRule

Eclass HasValueRefCheckRule extends DtoTypeCheckRule, MustNotNullCheckRule

 

2.模板设计

子模板需要提供方法签名模板、各数据类型校验流程模板

2.0.代码转写

进行元模型M2T设计,仅凭设计稿基本难以一次性写对,因此在这里开始代码人工转写。基本都是人工先转写一版,再照着转写的结果进行M2T代码的开发。

2.1.方法签名设计

根据目标描述,对校验行为分类,确定Java返回值和参数签名

1.1.1.打断策略、校验成功、不生成上下文|返回值void,结束方法运行
1.2.1.打断策略、校验失败、不生成上下文|返回异常,异常中不包含上下文信息
1.1.2.打断策略、校验成功、生成上下文|返回上下文,上下文中包含所有额外IO的原始结果变量
1.2.2.打断策略、校验失败、生成上下文|返回异常,异常中包含所有额外IO的原始结果变量
2.1.1.完整策略、校验成功、不生成上下文|返回值void,结束方法运行
2.1.2.完整策略、校验成功、生成上下文|返回上下文,上下文中包含所有额外IO的原始结果变量
2.2.1.完整策略、校验失败、不生成上下文|返回异常,异常中不包含上下文信息
2.2.2.完整策略、校验失败、生成上下文|返回异常,异常中包含所有额外IO的原始结果变量
2.2.3.完整策略、校验失败、不生成详细的校验结果|返回异常,异常中不包含详细错误信息
2.2.4.完整策略、校验失败、生成详细的校验结果|返回异常,异常中需要包含校验失败的字段以及错误信息

2.2.方法签名生成逻辑设计

[xxxx/]代表Acceleo中的元模板占位

Payload元模型节点定义

Payload.genClassName 生成类名

Payload.name Payload的名字

对Payload子类的实例对象aPayload做M2T转换规则设计如下:

2.3.数据类型校验设计

对任意属性 checkAttr(anAttr:Attr)

做对anAttr的type做类似于instance of的类型判断如下:

对字符串 checkStr(v->predicate(v))

对数值 checkNumber(v->predicate(v))

对布尔checkBool(v->predicate)

对引用类型来说 checkRef(ref->predicate(ref)) && ref.attrs.forEach(attr->checkAttr(attr))

MustNotNullCheckRule

2.1.1.字符串 str

NotEmptyCheckRule

Eclass LengthCheckRule

RegexCheckRule

2.1.2.数字

代码结构设计 https://github.com/956237586/realworld-mdd/blob/master/src/main/java/cn/hylstudio/mdse/demo/realworld/util/ValueUtils.java#L69

模板设计参考 https://github.com/956237586/hyldesigner-codegen/blob/main/src/cn/hylstudio/mdse/demo/gen/abstractCheckRule.mtl#L41

2.4.主校验流程设计

因限定了参数校验的范围,因此主流程可以固定下来,不会陷入无限接近代码的怪圈。

按前面所说校验分为三类依次执行,其中2和3可能产生CheckContext。

对指定对象的每个属性,依次获取子节点下+引用源子节点下的所有规则,并根据规则执行生成逻辑

20220130目前只取了当前子节点下的,未获取源子节点下的,CheckContext因和数据库有关,因此推迟

20210828.近期动态分享:MDSE实践计划及过程记录

背景&闲聊

(说起来好久没好好写过博客了,进到主页看到之前挖的坑没怎么填,我的内心居然毫无波动。一个是工作比较忙没啥精力更新,一个是沉迷原神hhhh)

最近同事离职,开发任务短期内变多,接手项目得快速的阅读代码、填坑、修bug,还得开发新功能。面临的问题:1.面对未知项目代码,快速分析、理清逻辑依赖、上手开发新功能/修复bug。2.简化工作中重复/规律性极强的操作

下班后不由得开始思考,有没有一种工具/方法能简化我日常的工作,让我专注于更有价值的事情上,而不是照着设计文档人肉翻译到Java代码。

看着床头一摞毕业后从未怎么翻过的书,里面有一本Model-Driven Software Engineering,大概意思是描述和设想了一套和目前主流完全不一样的软件开发流程,和近些年火起来的中台和低代码有点关系。上学时候觉得定义1:1的代码模型还不如直接写代码来的快,软件可视化蓝图的效果停留在机械的翻译java代码到思维导图的阶段。工作了几年发现大部分”重复”工作都是基于M1(model)层的,想做变革就得上升到M2(meta-model)甚至M3(meta-meta-model)才有可能。上网搜了搜相关资料发现了这个文章 https://blog.csdn.net/chljapan/article/details/88874430。看起来感觉很有意思,当时上学时候学习的MDSE,如今居然有人真的实现落地了一个执行引擎。项目代码:https://github.com/chljapan/KayaSmart/blob/main/README_ZN.md

尝试按照文档操作了下,发现的问题:这个方案使用了GME来做模型,在尝试下载安装的时候win10下会直接失败。并且看截图界面并不像个现代工具,并不符合我的审美。万里长征失败于第一步hhhhh。借鉴、改进kaya流程的想法也随之而来。因此对EMF相关技术做了部分调研,调研涉及的技术、项目都在后面记录了,有的翻译了一点官方文档的描述,有的懒得翻译只贴了网址。有能力来研究这些相关技术的人,必然是高手(除了我),想必也并不需要我来给出翻译hhhh。总体原则是能用图形界面的用图形界面编辑,否则考虑文本编辑。尽可能少写”代码”,找成熟或已有的方案组合使用来解决问题。

无奈的是,MDSE虽然发展了这么多年,但资料相比主流编程语言、AI、大数据之类的来说简直少的可怜,官方文档的描述也并不能完全相信用于指导学习,只能参考,甚至不得不翻源代码来解决问题。同时国内有能力做灵活的M2设计开发的人太少了,更不要说再限定在后台开发领域了。虽然近几年出现了不少号称低代码的平台,但都是各有各的限制,目前没看到一个完全通用的,看起来和前几年吹的中台更接近一些。个人认为,大部分应该都是堆人力堆出来的,掌握M2核心抽象设计技术的人应该不会轻易在网上露面。

根据调研得到的结果,反复阅读官方文档后,我尝试使用ecore tools制作了元模型,使用默认的树形编辑器尝试制作了模型,使用Acceleo做了代码生成。虽然只是生成几个包和几个简单Java类,但这让我对MDSE又重拾了信心hhhh。反观CS/SE的发展历史和编程语言的进化史,每一次的进化无一不是更加抽象、隐藏了更多细节,同时隐藏的细节部分交给机器(一般都是编译器)完成。

在制作过程中,好奇心驱使我打开了ecore和自定义的模型文件看了下原始内容,结果不出意外是XML格式的,里面引用对象时使用的语法也类似于xpath(此处存疑,不知道是不是xpath,但能看懂含义),果然上学时候好好学习还是有用的,当时没啥用的东西现在居然用到了hhhh。也就是说dtd和xslt之类的技术,在本次计划中也能派上用场。如果没有合适的工具,自行解析xml是一个绝对能达成目标的兜底方案。

在主流程demo走通了之后,因为自带的eclipse插件编辑模型太繁琐了,尝试使用sirus制作个图形化编辑器来方便使用。面临的问题就是需要自行编写odesign定义元模型到UI的映射定义。在尝试搜索寻找可复用的odesign例子时,无意间发现了一个MTM的github项目(https://github.com/tuiSSE/sirius-meta-editor),参考论文是A Generative Approach for User-Centered, Collaborative,Domain-Specific Modeling Environments。阅读了一部分发现居然有人做了个全套的工具集CINCO,并且还是web-based。人家不光做了全套的工具集,还借助M3的MTM做到了自动化生成所有配套工具,虽然目前还没下载完,看了下协议是eclipse public的,感觉可以直接拿来使用或改造。

这个论文值得好好阅读,个人认为人在SE过程中最大、最有价值的部分就是设计,这也是为什么科班教学课程的名字一直都是面向过程程序设计(C)/面向对象程序设计(Java/C++),编程语言的学习只是次要的,根据计算机底层原理设计程序如何执行才是重点。MDSE提出了这么多年,随着AI的发展,感觉距离完美的MDA越来越近了,只做设计这个目标有生之年有望达成。

目标

短期目标:通过简化的图形化/文本化编辑,持久化domain模型,自动生成数据库Entity、项目框架、通用/自定义业务逻辑等代码,减少后台业务的重复开发劳动

长久的目标,使用最佳的MDSD实践、不再编写代码

相关技术调研记录

Eclipse Modeling Framework (EMF)

元模型技术支持,主要是ecore,用于构建EMF model

https://www.eclipse.org/modeling/emf/

https://www.eclipse.org/modeling/emf/gettingstarted.php

https://eclipsesource.com/blogs/tutorials/emf-tutorial/

 

Graphical Editor Framework(GEF)

提供通用的模型编辑能力框架   图形化编辑功能基础能力

Sirius

基于EEF的图形化workbench生成,用于制作自定义EMF model的编辑器

https://www.eclipse.org/sirius

https://wiki.eclipse.org/Sirius/Tutorials/BasicFamily

自动生成参考文献  https://ieeexplore.ieee.org/document/9447062

参考项目https://github.com/tuiSSE/sirius-meta-editor

https://github.com/tuiSSE/sirius-meta-editor-architecture2sirius

graphiti

https://www.eclipse.org/graphiti/

glsp 图形化编辑协议,支持vscode

https://www.eclipse.org/glsp/

ecoreTool

基于sirius 提供自定义ecore模型的可视化化编辑,

https://www.eclipse.org/ecoretools/doc/index.html

Xtext

https://wiki.eclipse.org/Xtext

Xtext 是用来开发第三方文本型DSL的框架/工具,仅仅需要使用Xtext的EBNF(扩展的Backus-Naur范式)描述你自己的DSL语法,生成器会自动创建分析器、抽象语法树元模型 (基于EMF实现) 、完整的Eclipse文本编辑器

这个框架集成了来自Eclipse Modeling的以下技术:例如EMF, GMF, M2T 以及一部分的EMFT. 使用Xtext开发可以很快的得到反馈,以至于给存在的DSL增加特性只需很短的时间, 更新更复杂版本的编程语言可以被轻易实现,语言的开发从未如此简单

CINCO

使用MGL(Meta Graph Language)定义元模型,CINCO自动生成图形/文本编辑器的eclipse插件

https://cinco.scce.info/

https://cinco.scce.info/assets/documents/cinco-manual.pdf

CINCO 提供了多种语言来定义图形化DSL的元模型

  • Meta Graph Language MGL模型定义了模型的每个组件以及他们之间的关系
  • Meta Style Language MSL描述了MGL中节点、边、容器的形状和外观
  • CINCO Product Definition CPD 组合多个MGL来生成一个CINCO产品

pyro

CINCO的插件, 支持让CINCO生成定义模型的网页版IME

https://pyro.scce.info/

https://pyro.scce.info/assets/documents/pyro.pdf

Pyro使用CINCO元插件扩展来生成其他环境下的产品,基于CPD和它引用的MGL和MSL,Pyro基于基于web领先的技术提供和本地应用相同的用户体验

  • JointJS 基于SVG的图形可视化和交互,开发者为 Client.IO (JointJS).
  • Dart 面向客户端的开发语言,开发者为Google (Dart Lang).
  • Angular  前端的单页应用框架,开发者为Google (Angular).
  • AngularDart web应用框架 (AngularDart)
  • DyWA 动态web应用程序框架在OOP上下文环境中提供了快速原型设计以及类型和对象的动态松散耦合管理

DIME (DyWA Integrated Modeling Environment)

开发web应用的集成建模环境

https://scce.gitlab.io/dime/content/introduction/

TODO:这里面有现成的对数据流和控制流的建模,可以考虑想办法找到它的ecore直接继承、扩展、修改、参考借鉴等

Object Constraint Language

声明式语言(Declarative),用于约束和查询模型上的元素

https://help.eclipse.org/latest/index.jsp?nav=%2F65

OCL Project

https://projects.eclipse.org/projects/modeling.mdt.ocl

该项目提供了符合 OMG 标准基于EMF模型的实现

  • 为解析执行OCL约束和EMF上的查询定义了API
  • 为OCL抽象语法模型定义了Ecore和UML的实现,包含对已解析的OCL表达式的序列化
  • 提供了用于分析和转换OCL表达式的抽象语法树模型的API
  • 提供了可扩展的客户端API,可用于语法分析器的自定义解析和执行环境

OCL分析器基于LALR生成,该项目提供了如下特性

  • 提供交互式OCL终端来执行模型上的查询
  • 提供嵌入Ecore元模型的OCLinEcore Xtext  编辑器
  • 提供完整的OCL编辑器完善OCL文档来补充元模型信息
  • 提供基础的OCL编辑器,用于独立的OCL表达式

https://wiki.eclipse.org/OCL/OCLinEcore

Ecore编辑器可以方便的维护一系列的OCL约束,但它作为OCL 的IDE来说显然有一些局限性

  • 需要考虑维护EAnnotations
  • 在Properties View下OCL表达式难以阅读
  • 无法检测到OCL的语法和语义错误

OCLinEcore Editor 克服了这些问题,可以支持嵌入式的维护ecore的Annotation

安装方法,选择“OCL Examples and Editors”安装即可

注意编辑器比较新(看文档已经是8年前的了,还新??),这里做的是名字引用的验证而不是他们的类型验证,这个还在实现中(有生之年系列?)。如果有一些错误误报的话,需要直接保存并重新打开编辑器。OCLinEcore文本可以在语法、语义校验和自动提示的帮助下来编辑

空格和OCL注释将被存入Ecore EAnnotations中,如果你不想保存注释,在右侧选择另存为OclInEcore即可

OCLinEcore参考严格的OCL表达式和OMG语法语法推断,但完整的实现 TBD(多半是鸽了)。示例编辑器中的语法目前还存在争议,在Helios版本后的发布中可能被修改

papyrus

https://www.eclipse.org/papyrus/index.php#applications

https://www.eclipse.org/papyrus/documentation.html

Eclipse Papyrus是一个 UML工具,它提供了非常高级的基础设施用于定制化工具

UML designer

基于sirius 提供通用UML图的可视化编辑

http://www.umldesigner.org/

使用标准的 UML2 元模型 , 提供了方便的方法来融合UML和自定义领域模型. 你可以扩展已有的定义来无缝同时使用自定义模型和UML,你可以根据需要修改每一个图形

https://www.eclipse.org/papyrus/index.php#applications

Model Transformation 模型转换,低精度EMF model转化为高精EMF model

https://www.eclipse.org/modeling/transformation.php

acceleo

自定义代码生成器 需要ecore model和代码模板

https://www.eclipse.org/acceleo/overview.html

https://wiki.eclipse.org/Acceleo/Getting_Started

 

总体思路

1.通过对常用概念建模,制作自定义元模型M2 、低精M1、高精M1

工具:EcoreTool、OCLinEcore Editor、UML Designer、CINCO

插件开发不重启eclipse

https://dzone.com/articles/reload-your-plugins-without

https://wiki.eclipse.org/FAQ_How_do_I_make_my_plug-in_dynamic_aware%3F

https://wiki.eclipse.org/FAQ_How_do_I_make_my_plug-in_dynamic_enabled%3F

CINCO支持引用外部ecore:

https://gitlab.com/scce/cinco/-/wikis/Meta-Graph-Language#nodescontainer-that-reference-model-elements-defined-in-arbitrary-ecore-metamodels

2.(可选)通过低精model转化为高精model   或借助Sirius控制model的实现精度

3.完成代码框架和常用类的生成

工具:acceleo、OCL

独立运行方法 https://stackoverflow.com/questions/1714778/programmatically-running-emf-code-generation

call Eclipse from the command line. Something similar to this:

This command will start Eclipse silently and will generate mode code (-model), edit plug-in (-edit), editor plug-in (-editor), and the test plug-in (-tests). Leave out any of these parameters if they are not needed.

4.手动剩余实现代码

具体实现路线

1.原始的Ecore

ECoreTool + Acceleo

2.扩展Ecore定义

参考 https://blog.csdn.net/cuxiong8996/article/details/107155073

进度

20210911 路线1

目前通过Sirius EcoreTool生成ecore+genmodel元模型和插件、通过编辑器插件生成模型、通过acceleo生成代码流程已走通

元模型公开:https://github.com/956237586/hyldesigner

acceleo代码模板公开:https://github.com/956237586/hyldesigner-codegen

问题:Sirius EcoreTool的Eclipse版本和Acceleo的Eclipse版本不一致,通过最新Eclipse手动安装插件会提示错误,有依赖是冲突的

20211023

之前Demo一般都会用Todo-App来做,近些年都换成realworld了,所以我也用realworld来作为这种开发方式的Demo。

realworldDemo公开:https://github.com/956237586/realworld-mdd

其实实现方式和正常编程没啥区别

1.按API文档定义按我自己的风格正常手写一遍实现,包括测试用例

2.建立个src-gen文件夹使用同样的包名,调整codegen和meta-model的定义,把codegen生成的代码放这里。

3.通过git或idea对比文件变动,让codegen生成的代码在不丢失可读性的前提下和手写的代码完全一致or逻辑等价

4.重复1-3,直到整个项目完成

目前已经成功实现了api请求参数和返回值的自动生成、Controller骨架生成

而在生成Controller实现逻辑的时候遇到了困难。接口的实现逻辑从底层来看整体可以划分为控制流和数据流,数据流就是例如各种Payload、Dto、Entity之间的的转化代码,控制流是在这些变化的基础上增加跳转、组合逻辑。因此无论怎么抽象都绕不过控制流和数据流的建模,而这部分目前无论怎么抽象都是很复杂的。会直接导致编辑模型时要提供的信息量无限接近于文本形式的代码,反而不会提高效率or简化开发过程,甚至会起到反作用。控制流和数据流的框架代码可以考虑自动生成,而实现过程的完整建模如前面所说的原因不再打算继续做无用功。

观察了已有的实现方案,例如DIME,它是提供给非技术人员用于实现后台服务的,所以它实现了完整的图形化。图形的编辑方式目前主流是拖拽+修改属性,根据我的经验这种方式在通用任务编程任务中并不好用。反而必须得是限定到某一类小任务上,图形化的优势才能发挥出来。而作为专业后台开发,面对多变的复杂业务流程时以这种形式阅读/检索/编辑”代码”并不会更高效,可能会适得其反。DIME的官方demo效果如下图。

因此下一步计划设计plugin架构来支持用户编写的代码和自动生成的代码相互调用,只把重复的实现部分交给自动生成,复杂控制流的编写依然需要采用原来的方式进行编码。

在这里其实常见的设计模式都可以使用,而不同场景下的plugin需要选择合适的设计模式。

https://github.com/956237586/realworld-mdd/blob/master/src/main/java/cn/hylstudio/mdse/demo/realworld/service/login/impl/BizLoginServiceImpl.java#L91

可以看到登录主流程可以分为4个阶段:这其中除了3以外,都有非常强的规律,我们分别来讨论

1.参数检查 https://blog.hylstudio.cn/archives/841

2.参数解析、转换

3.主登录流程doLogin

4.登录结果转换

参考资料

书籍来源

Model-Driven Software Engineering in Practice

Eclipse Modeling Framework 2nd Edition

文献

Creation of domain-specific languages for executable system models with the Eclipse Modeling Project

A Generative Approach for User-Centered, Collaborative, Domain-Specific Modeling Environments

Pyro: Generating Domain-Specific Collaborative Online Modeling Environments

A Tutorial Introduction to Graphical Modeling and Metamodeling with CINCO

 

值得关注的作者 国外有 Philip Zweihoff, Bernhard Steffen, ‪Stefan Naujokat‬几个大佬,国内的 chljapan(github看到的,不知道真名)、刘建斌教授

概念定义 https://en.wikipedia.org/wiki/Meta-Object_Facility

xsd持久化问题参考 https://www.cnblogs.com/demonrain/p/3559525.html

学习笔记1,发现是翻译自Eclipse Modeling Framework 2nd Edition,直接参考原文

目录https://blog.csdn.net/u012521340/category_7023464.html

学习笔记2

目录 https://blog.csdn.net/kevinz26/category_325754.html

 

2020javaweb开发环境搭建

1.前言

目前常用的IDE有eclipse、idea、vscode,本文暂时介绍前两个

tomcat、mysql、jdbc驱动无论哪个方案都需要

2.基础组件下载

2.1.tomcat9

不要用10,eclipse还不支持

https://tomcat.apache.org/download-90.cgi

2.2.mysql

https://dev.mysql.com/downloads/installer/

2.3.jdbc

https://dev.mysql.com/downloads/connector/j/

3.eclipse方案

目前最新eclipse运行本身需要java11以上,这里为了方便使用openjdk15

3.1.IDE下载

下载地址  https://www.eclipse.org/downloads/packages/

3.2.jre下载

如果你本地有,可以不用下载

下载地址 http://jdk.java.net/15/

3.3.配置

见视频

3.4.helloworld

见视频

4.idea方案

4.1.idea下载

下载地址 https://www.jetbrains.com/zh-cn/idea/download/download-thanks.html?platform=windows&code=IIC

4.2.tomcat插件下载

见视频

4.3.个人偏好配置(可选)

见视频

4.4.helloworld

见视频