IIDP开发规范
目录
前言 3
一、 编程规范 4
二、 测试规范 20
三、 授权管理规范 35
四、 版本管理规范 35
五、 工程结构规范 38
六、 部署规范 43
前言
《IIDP开发规范》是赛意工业软件及物联子公司技术平台研发团队,基于IIDP低代码平台设计、开发和测试过程中,汇集团队内部和外部交付团队集体的智慧结晶和开发经验总结,经历了从零开始自研IIDP低代码平台,多次大规模一线实战的检验及不断的完善,系统化地整理成册,回馈给开发者的一份开发规范。现代软件行业的高速发展对开发者的综合素质要求越来越高,因为不仅是编程知识点,其它维度的知识点也会影响到软件的最终交付质量。企业数字化转型,需要部署大量企业级应用,随着业务的发展,需求无法得到及时响应,大大增加了数字化转型的成本,这也是我们开发IIDP的初衷,极大地给开发者带来了很多开发方面的便利和快捷,同时为了提高开发者编码的质量和可靠性,我们也约定了很多规范,比如:对象命名的混乱带来代码的不好维护;模型设计的不规范带来从一开始就导致设计上的混乱给;数据库的表结构和索引设计缺陷可能带来软件上的架构缺陷或性能风险;工程结构混乱导致后续维护艰难;没有鉴权的漏洞代码易被黑客攻击等等。所以本手册以IIDP开发者为中心视角,划分为编程规范、测试规范、授权管理规范、版本管理规范、工程结构规范、部署规范等几个维度,再根据内容特征,细分成若干二级子目录。根据约束力强弱及故障敏感性,规约依次分为【强制】、【推荐】、【参考】三大类。对于规约条目的延伸信息中,"说明"对内容做了适当扩展和解释;"正例"提倡什么样的编码和实现方式;"反例"说明需要提防的雷区,以及真实的错误案例。 现代软件架构都需要协同开发完成,高效协作即降低协同成本,提升沟通效率,所谓无规矩不成方圆,无规范不能协作。众所周知,制订交通法规表面上是要限制行车权,实际上是保障公众的人身安全。试想如果没有限速,没有红绿灯,谁还敢上路行驶。对软件来说,适当的规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率。代码的字里行间流淌的是软件生命中的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升质量意识。
希望IIDP开发者通过阅读本手册,能够充分利用IIDP平台进行开发,避免常见的错误和陷阱,写出高质量的代码,提高开发效率。让我们一起码出高效、码出质量的软件系统。
版本号 | 指定团队 | 更新日期 | 备注 |
---|---|---|---|
0.0.1 | IIDP开发团队 | 2023.9.10 | 正在编写中 |
1.
编程规范
1.
命名规范
- 【强制】app命名规范。应用名称必须唯一,必须在app.json中定义,使用业务含义的应 用名称作为前缀。(长度,允许包含哪些字符,开头。保留关键字,平台预留)
说明:在IIDP平台中,app是名称唯一标识,确保应用名称的唯一性,便于识别和管理。
正例:iot_net / iot_base / smi_base / smi_redis / snest_tenant / snest_log / snest_base 等大的项目名称作为前缀,具体的业务场景作为后缀名称,具有业务含义,也能唯一区分。
反例:net / base / log 等模糊或不具有业务含义的名称很容易重名,且不知道是具体哪个项目的名称。
- 【强制】模型命名规范。模型名称必须唯一,并在注解中定义。推荐使用业务含义的模型名称作为前缀。
说明:在IIDP平台中,模型名称是名称唯一标识,对应着数据库中的表名称,使用下划线拼接,控制字符串长度,确保应用名称的唯一性,便于识别和管理。
正例:tenant_action_rule / tenant_rbac_organization 等大的项目名称作为前缀,具体的业务场景作为后缀名称。
反例:net_model / base_model / logModel 等,不需要加model后缀,不需要采用驼峰拼接,且不知道是具体哪个项目的名称。
- 【强制】定义模型时,格式要求为 public class XXXClass extends BaseModel<XXXClass>
说明:用户自定义的类继承自BaseModel<T>,其中T为当前实体类的类型参数,实体类应该继承自BaseModel,以获得基本的模型功能和属性。
正例:
public class TenantOrg extends BaseModel<TenantOrg> {
_// __类的定义__..._
}
- 【强制】Property命名规范。Property命名采用驼峰的形式。
说明:为了保持代码的一致性和可读性,Property的命名应该使用驼峰命名法。驼峰命名法是一种命名约定,其中单词之间没有空格,每个单词的首字母大写,其他字母小写。
正例:如companyName。这样可以提高代码的可读性和一致性。
@Validate.NotBlank
@Validate.Pattern(regexp = "^.{2,200}$")
@Property(displayName = "公司名称")
private String companyName;
- 【强制】Property注解的dataType数据类型,必须选择以下类型之一:
BigDecimal: 用于存储大数值
Boolean: 用于存储true或false值
Date: 日期类型,默认格式为yyyy-MM-dd
DateTime: 时间日期类型,默认格式为yyyy-MM-dd HH:mm:ss
Double: 双精度浮点型。精度可由位数和小数位数对来定义,默认精度为2
Selection: 选择类型,包括常量枚举、下拉框单选、下拉框多选、下拉框联动
File: 文件类型,文件类型可由contentType指定
Integer: 整型
Long: 长整型
List: 存储List对象,以JSON格式存储数据,在读取时反序列化为List对象
Map: 存储Map对象,以JSON格式存储数据,在读取时反序列化为Map对象
Object: 存储对象,以JSON格式存储数据,在读取时反序列化为对象
String: 字符串类型
Text: 长文本类型
根据上述新增规范,可以根据需要选择合适的类型来注解Property的值
- 【强制】validatealidate 提供的校验方式为在类的属性上加入相应的注解来达到校验的目 的。validator提供的用于校验的注解如下:
@NotBlank | 不能为null |
---|---|
@Pattern(regex=) | 被注释的元素必须符合指定的正则表达式 |
检查是否是一个有效的email地址 | |
@Max | 该字段的值只能小于或等于该值 |
@Min | 该字段的值只能大于或等于该值 |
@Size(min=,max=) | 检查所属的字段的长度是否在min和max之间,只能用于字符串 |
@Null | 能为null |
@Unique | 唯一校验。默认只校验当前字段。可以设置 properties,指定多个字段联合唯一 |
- 【强制】getter setter 命名规范。get set 方法的列名称定义的是下划线拼接(比如下例中 的role_name),但是实际是roleName驼峰命名的格式。
说明:为了保持代码的一致性和可读性,get和set方法应该使用驼峰命名法。驼峰命名法是一种命名约定,其中单词之间没有空格,每个单词的首字母大写,其他字母小写。
正例:
@Property(columnName = "role_name", displayName = "角色名称")
private String roleName;
public String getRoleName(){
return (String) get("roleName");
}
public void setRoleName(String roleName){
set("roleName", roleName);
}
根据上述新增规范,getter和setter方法的命名应采用驼峰命名法,如getRoleName()和setRoleName(String roleName)。这样可以提高代码的可读性和一致性。
为了方便开发者快速生成getter setter 代码,下面给出配置IDEA getter setter 代码模板。
getter模板:
#if($field.modifierStatic)
static ##
#end
$field.type ##
#if($field.recordComponent)
${field.name}##
#else
#set($name = $StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field, $project))))
#if ($field.boolean && $field.primitive)
is##
#else
get##
#end
${name}##
#end
() {
return ($field.type)this.get("$field.name");
}
Setter 模板:
#set($paramName = $helper.getParamName($field, $project))
#if($field.modifierStatic)
static ##
#end
$classname set$StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field, $project)))($field.type $paramName) {
#if ($field.name == $paramName)
_#if (!$field.modifierStatic)_
this._##_
_#else_
$classname._##_
_#end_
#end
set("$field.name", $paramName);
return this;
}
- 【强制】MethodService 命名规范。注意与默认的方法重名,内置的方法目前有create delete select update search 等,所以要明确要重写父类的方法,还是定义新方法。
说明:重写父类的方法时,一定要明确是继承还是扩展,如果想指定调用父类的方法,需要指明callSuper.
- 【强制】模型注解命名规范。哪些注解是必填的,不填写则警告。
说明:模型注解。
1.
常量定义
- 【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
正例:通过枚举值定义模型类型
public static enum ModelType {
Default,
Define,
Buss,
Memory,
Data,
Cache,
Config,
Tree,
Template,
View;
private ModelType() {
}
}
反例:模型名称用字符串,调用方法也是字符串。导致其他地方使用这个模型名称时,需要重新再写一遍字符串,如果修改了一处,另外一处不会自动同步修改。
this.getMeta().get("rbac_tenant").callSuper(Tenant.class, "create", valuesList);
- 【强制】枚举值是确定不变的值列表;平台建立了dict app,支持动态配置字典类型与字 典值,java sdk用注解的方式可以便捷进行关联使用。
1.
模型规范
- 【参考】基于元模型驱动的平台核心思想:一切皆模型、模型可扩展可继承。
说明:关于平台的几点说明:
- 每个元模型都有唯一身份证;
- 平台的模型不仅提供属性、服务等能力,模型之间还可以方便进行继承、扩展;
- 模型还分了业务模型(缓存与db的存储与访问能力)、数据模型(内存存储和访问能力)、树状模型(行级存储与访问能力)等;
-
业务模型根据是否抽象来确定是否需要建立表;
-
【强制】通过@Model注解声明模型
-
【强制】通过extends BaseModel标识模型具备元模型能力
-
【推荐】视图模型解决的场景
- 页面一样,处理逻辑不一样
很多时候一个模型可能对应的不只是一个菜单,可能根据不同的列显示要求对应多个菜单。如果仅仅是列显示的变化,而crud的逻辑是一样的,这样可以通过权限加以控制;视图模型用在同一个模型,crud逻辑不一样的场景。
- 多模型数据聚合、按字段分组合并
可以根据关联模型的过滤条件进行检索,查询多个模型的数据、并支持按字段进行分组后将字段多行数据合并。
- 【强制】模型继承规范。不推荐使用多继承。
说明:通过@Model指定name和parent,当name和parent不一致时为模型继承关系,parent是父模型名称,name是子模型的名称。新模型将继承原模型的所有字段、方法和服务。示例如下:
@Model(name = "a_model",parent = "b_model")public class AModel extends TestUser{}
@Model 中parent是个数组,可以通过设置多个parent,表示多继承,该模型会自动继承多个父模型。
继承服务移除:子模型继承了父模型,但是不想具备父模型中个别的服务,这时需要用到服务移除@Service(remove=["serviceName1","serviceName2"]
- 【强制】扩展规范。
说明:平台最核心的能力就是扩展能力。平台可以通过模型扩展增强或改变原有模型内部定义的服务、属性,甚至模型自身的元数据。
正例:模型内部crud扩展。常见场景说明:想在平台提供默认的crud能力的基础上,做一些业务操作。通过在业务模型中定义与默认方法签名相同的方法,来重写默认逻辑。 通过@Model指定name和parent,当name和parent一致时为模型扩展关系。扩展已定义的模型,为已有模型增加能力,或改变已有模型的现有能力,示例如下:
// __新增
public void create(@Spec(doc = "k v") List<Map<String, Object>> valuesList) {
}
// __修改
public int update(@BaseService.Spec(doc = "k v") Map<String, Object> values) {
…
}
// __删除
public boolean delete() {
…
}
// __查询
public List<Map<String, Object>> search(@Spec(doc = "过滤器") Filter filter,
@Spec(doc = "多个属性") List\<String\> properties,
@Spec(doc = "初始位置") Integer limit,
@Spec(doc = "记录数") Integer offset,
@Spec(doc = "排序") String order) {
…
}
// __统计数量
public long count(@Spec(doc = "过滤") Filter filter) {
…
}
-
【强制】唯一索引
-
【强制】约束。(循环依赖)
-
【强制】ER关系
说明:尽量不要在循环中获取ER关系字段,平台对当前线程中的N:1按id进行了缓存,在循环中多次查询可能会命中缓存,但是存在1+N次查询的风险。建议分2次查询后进行组装返回最终结果。
正例:
- OneToMany 一对多
@OneToMany
private List<TestUser> userList;
- ManyToOne 多对一
@ManyToOne(displayName="组织机构")@JoinColumn(name="org_id", referencedColumnName = "id")
private TestOrg org;
- ManyToMany 多对多
@ManyToMany
@JoinTable(name = "role_user", joinColumns = @JoinColumn(name = "role_id",nullable = false),
inverseJoinColumns = @JoinColumn(name = "user_id", nullable = false))
private List<TestUser> userList;
-
【强制】数据库连接
-
【强制】ORM使用
-
【强制】Filter过滤条件
-
filter表达式
filter表达式通常用来筛选数据记录。它们使用波兰表示法语法,以便于将它们解析后生成对应的SQL WHERE数据库筛选语句。 filter通常为一个数组,数组元素为过滤条件,每个条件是一个三元表达式,例如:["&",["state","=","confirm"],["user_id","in",[1,2,3]]] filter多个条件的逻辑运算使用了"波兰表示法",波兰表示法的特定是操作符置于操作数前,运算顺序为:从左至右读入表达式,遇到一个操作符后跟随两个操作数时,则计算之,然后将结果作为操作数替换这个操作符和两个操作数;重复此步骤,直至所有操作符处理完毕。
- filter写法
filter表达式是一个条件列表,每个条件是一个形如["field_name", "operator", value]的数组。
正例:
[
"|",
[
"message\_follower\_ids",
"in",
[
"1",
"2",
"3"
]
],
"|",
[
"user\_id",
"=",
"1000"
],
[
"user\_id",
"=",
false
]
]
filed_name 是需要筛选的字段,它可以使用点(.)来访问关系模块的字段。 value 是一个表达式的值。它可以使用字符值,比如:字符串,数字,布尔值,或则列表、某个字段。 operator 可以为: 常用的操作符:<,>,<=,>=,=,!=。 "like"匹配一个"%value%"的字符串。"ilike"与此类似但不区分大小写。"not like"和"not ilike"也可以使用 "child_of","parent_of"在层级关系中,筛选子集 "in"和"not in"筛选是否在一个列表里面,所以,给的值应该是个list。当在"to-many"的关系字段中,"in"的作用和contains的作用一样。
- Filter的操作符
操作符主要有如下类型。逻辑运算符,主要用于多个条件处理,逻辑运算符链接。逻辑运算符作为前缀放置于条件前面。: " | "(or) "&" (and) "!"(no)" 默认逻辑运算符为"&" |
操作符 | 说明 |
---|---|
=,!=,>,>=,<,<= | 比较运算,等于,不等于,大于,大于等于,小于,小于等于 |
like | 模糊匹配,可以使用通配符,,百分号"%"匹配零或者多个字符 |
ilike | 类似like,不区分大小写 |
not like | 模糊不匹配的 |
in | 包含,判断值是否在元素的列表里面 |
not in | 不包含,判断值是否不在元素的列表里面 |
child_of | 判断是否value的子记录 |
parent_of | 用于有 父子关系的模型,13版本开始使用。 在旧版本使用 parent_left, parent_right |
- Filter使用的算法是波兰表达式
计算的核心思想:运算波兰表达式时,无需记住运算的层次,只需要直接寻找第一个运算的操作符。以二元运算为例,从左至右读入表达式, 遇到一个操作符后跟随两个操作数时,则计算之,然后将结果作为操作数替换这个操作符和两个操作数;重复此步骤,直至所有操作符处理完毕。 简单来说,波兰表示法是一种操作符置于操作数前,并且不需要括号仍然能无歧义地解析表达的方法。
- Filter的使用,大写的AND,小写的and不一样。
预加载
级联
分库分表
依赖项,数据依赖,配置,种子数据,app内聚,装完就全部有了。任意一个环境装app都可用。
机制问题。扩展对方的种子数据(看板,数据字典)
1.
在线IDE使用规范
2.
SDK使用规范
- 熟悉SDK文档和功能:
在开始使用SDK之前,仔细阅读官方文档,了解SDK提供的功能和用法。
理解SDK的设计理念和工作原理,以便正确地使用和集成SDK。
- 遵循SDK的最佳实践:
官方文档已提供一些最佳实践和推荐的使用方式,尽量遵循这些指南。
遵循SDK的设计模式和约定,以便与其他开发者更好地协作。
- 使用适当的错误处理机制:
当使用SDK的方法或者函数时,要注意捕获和处理可能发生的异常。
根据SDK提供的错误码或者异常类型,进行适当的错误处理,例如记录日志、返回错误信息等。
- 避免滥用SDK:
只使用SDK提供的必要功能,避免滥用SDK的高级功能,以免增加复杂性和性能开销。
不要过度依赖SDK,尽量保持代码的灵活性和可扩展性。
- 使用SDK提供的扩展点:
如果SDK提供了扩展点或者插件机制,可以使用这些机制来自定义功能或者扩展SDK的行为。
- 更新和升级SDK:
定期检查SDK的新版本和更新,了解新功能、修复的问题和性能改进。
在合适的时机,考虑升级到新版本的SDK,以获得更好的功能和性能。
- 与SDK开发者保持互动:
如果SDK有相关的群聊或者社区,可以积极参与其中,与其他开发者交流经验和解决问题。提交反馈和建议,帮助SDK的改进和发展。(外网可访问的在线文档,反馈和建议的途径)
- IIDP SDK使用规范
api调用 filter crud 与前端页面交互
总之,SDK使用规范的核心是熟悉SDK的功能和用法,并遵循官方文档中的建议和最佳实践。合理地使用SDK,并与其他团队成员保持一致的使用方式,可以提高代码的可读性、可维护性和可扩展性。
1.
日志规范
- 【强制】IIDP引擎日志规范。
说明:IIDP引擎对通用的日志接口进行封装,规范日志打印格式,自动追加引擎相关日志信息,比如自动最佳model service等信息,自动可知道访问的是哪个model哪个service,基于这种结构化的日志,便于后续的日志分析。
- 【强制】在日志输出中包含有用的上下文信息。
说明:日志应该包含有助于定位问题的上下文信息,例如时间戳、线程ID、请求ID、关键参数值等。
正例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void doSomething(int id) {
logger.info("Received request with ID: {}", id);
_// __执行业务逻辑_
}
}
反例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void doSomething(int id) {
logger.info("Received request"); _// __缺少关键信息_
_// __执行业务逻辑_
}
}
- 【强制】使用适当的日志级别。
说明:根据日志的重要性和信息量选择适当的日志级别,例如使用DEBUG
级别进行调试和开发阶段,使用INFO
级别进行常规信息输出,使用WARN
级别进行警告信息输出,使用ERROR
级别进行错误信息输出。 (动态提升日志的级别,指定模型的日志级别)
正例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void doSomething() {
logger.debug("Debug message"); _// __调试信息_
logger.info("Info message"); _// __常规信息_
logger.warn("Warning message"); _// __警告信息_
logger.error("Error message"); _// __错误信息_
}
}
反例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void doSomething() {
logger.info("Debug message"); _// __错误的日志级别_
logger.info("Error message"); _// __错误的日志级别_
}
}
- 【推荐】在日志输出中包含异常信息。
说明:在捕获和处理异常时,应该将异常信息记录在日志中,以便更好地了解问题的原因。
正例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void doSomething() {
try {
_// __执行业务逻辑_
} catch (Exception e) {
logger.error("An error occurred", e); _// __记录异常信息_
}
}
}
反例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void doSomething() {
try {
_// __执行业务逻辑_
} catch (Exception e) {
logger.error("An error occurred"); _// __缺少异常信息_
}
}
}
- 【推荐】避免在循环中频繁输出日志。
说明:在循环中频繁输出日志会导致日志文件过大,降低系统性能。应该根据实际需要选择适当的日志输出频率。
正向例子:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void processItems(List\<Item\> items) {
for (Item item : items) {
_// __处理每个项目_
logger.debug("Processing item: {}", item.getId());
}
}
}
反例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void processItems(List\<Item\> items) {
for (Item item : items) {
_// __处理每个项目_
logger.debug("Processing item: {}", item.getId());
logger.debug("Item processed"); _// __频繁输出日志_
}
}
}
- 【推荐】使用合适的日志框架和配置。
说明:选择适合项目需求的日志框架,并进行合适的配置。常见的日志框架包括Log4j、Logback和Slf4j等。
示例:使用Slf4j和Logback作为日志框架,并配置合适的日志级别和输出格式。(平台封装,对外提供平台规定的接口)
<!– pom.xml –>
<dependencies>
\<dependency\>
\<groupId\>org.slf4j\</groupId\>
\<artifactId\>slf4j-api\</artifactId\>
\<version\>1.7.32\</version\>
\</dependency\>
\<dependency\>
\<groupId\>ch.qos.logback\</groupId\>
\<artifactId\>logback-classic\</artifactId\>
\<version\>1.2.6\</version\>
\</dependency\>
</dependencies>
<!– logback.xml –>
<configuration>
\<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"\>
\<encoder\>
\<pattern\>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n\</pattern\>
\</encoder\>
\</appender\>
\<root level="INFO"\>
\<appender-ref ref="CONSOLE"/\>
\</root\>
</configuration>
- 【推荐】使用有意义的日志消息。
说明:日志消息应该清晰、简洁且有意义,以便于开发人员和运维人员理解日志的含义。
正例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void processOrder(Order order) {
logger.info("Processing order: {}", order.getId());
_// __处理订单逻辑_
}
}
反例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void processOrder(Order order) {
logger.info("Method called"); _// __无意义的日志消息_
_// __处理订单逻辑_
}
}
- 【推荐】避免在生产环境中输出过多的调试信息。
说明:在生产环境中,应该避免输出过多的调试信息,以减少日志文件大小和系统性能开销。可以使用条件判断或配置项来控制是否输出调试信息。
正例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
private static final boolean DEBUG\_ENABLED = false; _// __控制调试信息输出的开关_
public void doSomething() {
if (DEBUG\_ENABLED) {
logger.debug("Debug message"); _// __只在调试模式下输出_
}
_// __执行业务逻辑_
}
}
反例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void doSomething() {
logger.debug("Debug message"); _// __无条件输出调试信息_
_// __执行业务逻辑_
}
}
- 【推荐】使用适当的日志输出格式。
说明:选择合适的日志输出格式,使日志易于阅读和解析。常见的格式包括普通文本格式、JSON格式和XML格式等。
正例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void doSomething() {
String message = "Log message with dynamic data";
logger.info("{}", message); _// __使用占位符输出日志消息_
_// __执行业务逻辑_
}
}
反例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void doSomething() {
String message = "Log message with dynamic data";
logger.info(message); _// __直接输出日志消息_
_// __执行业务逻辑_
}
}
- 【推荐】定期审查和清理日志文件。
说明:定期审查和清理日志文件可以避免日志文件过大,占用过多的磁盘空间。可以根据实际需求和存储能力,设置合适的日志保留期限和清理策略。
示例:使用日志框架提供的日志轮转和清理功能,定期清理过期的日志文件。
<!– logback.xml –>
<configuration>
\<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"\>
\<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"\>
\<fileNamePattern\>logs/example.log.%d{yyyy-MM-dd}\</fileNamePattern\>
\<maxHistory\>30\</maxHistory\> _\<!-- __保留最近__ 30 __天的日志文件 __ --\>_
\</rollingPolicy\>
\<encoder\>
\<pattern\>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n\</pattern\>
\</encoder\>
\</appender\>
\<root level="INFO"\>
\<appender-ref ref="FILE"/\>
\</root\>
</configuration>
- 【推荐】在关键路径和重要功能中增加详细的日志输出。
说明:在关键路径和重要功能中增加详细的日志输出可以帮助跟踪和排查问题。可以根据实际情况,在关键的方法、条件判断和异常处理等位置添加详细的日志输出。
正例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void processOrder(Order order) {
logger.info("Processing order: {}", order.getId());
_// __执行订单处理逻辑_
logger.info("Order processed successfully: {}", order.getId());
}
}
反例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExampleClass {
private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class);
public void processOrder(Order order) {
logger.info("Processing order"); _// __缺少关键信息_
_// __执行订单处理逻辑_
logger.info("Order processed"); _// __缺少关键信息_
}
}
这些规范和示例可以帮助您在日志打印功能中提高质量和可读性。请根据您的项目需求和团队约定,选择适合的规范并进行实施。
1.
测试规范
1.
单元测试
- 【强制】编写独立、可重复执行的单元测试。
说明:单元测试应该是独立的、不依赖外部资源的测试,可以在任何环境下重复执行。确保每个单元测试之间相互独立,不会相互影响,提高测试的可靠性和可维护性。
- 【强制】使用适当的测试框架和断言库。
说明:选择适合项目的测试框架(如JUnit、TestNG等)和断言库(如AssertJ、Hamcrest等),以便编写清晰、简洁的测试代码,并提供丰富的断言方法来验证测试结果的正确性。
- 【强制】 测试覆盖率达到预定目标。
说明:测试覆盖率是衡量测试代码覆盖业务代码的程度的指标。根据项目的需求和复杂度,设定合理的测试覆盖率目标,并确保单元测试覆盖率达到或超过这个目标。
- 【强制】 每个单元测试应该只测试一个功能点或场景。
说明:每个单元测试应该聚焦于测试一个特定的功能点或场景,避免将多个功能点或场景混合在一个测试中。这样可以提高测试的可读性和可维护性,并能更准确地定位问题。
- 【强制】 使用合适的测试数据进行测试。
说明:在编写单元测试时,应该使用合适的测试数据来覆盖不同的情况和边界条件,以验证代码在各种情况下的正确性。包括正常情况、边界情况、异常情况等。
- 【强制】验证预期的行为和结果。
说明:每个单元测试应该明确验证预期的行为和结果,通过断言来判断测试是否通过。确保测试代码能够准确地验证被测试代码的行为和结果。
- 【强制】 针对可能出现的异常情况进行测试。
说明:在编写单元测试时,应该针对可能出现的异常情况进行测试,以验证代码在异常情况下的处理能力。包括输入参数为空、越界访问、异常抛出等。
- 【强制】 定期运行单元测试,并及时修复失败的测试。
说明:单元测试应该定期运行,以确保代码的质量和稳定性。当单元测试失败时,应该及时修复失败的测试,并确保测试通过。这样可以保证代码的可靠性和可维护性。
- 【建议】 使用测试替身(Mock、Stub等)来隔离外部依赖。
说明:在单元测试中,应该使用测试替身(如Mock、Stub等)来隔离外部依赖,以便更好地控制测试环境和验证被测试代码的行为。通过模拟外部依赖的行为,可以更方便地编写和执行单元测试。
- 【建议】 使用参数化测试来减少重复代码。
说明:参数化测试是一种通过传入不同的参数来执行相同测试逻辑的方式,可以减少重复的测试代码。通过使用参数化测试,可以更高效地编写和维护单元测试。
下面给出详细的正向例子和反向例子来说明单元测试的规范。
正例:(使用JUnit和AssertJ):
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
public class CalculatorTest {
@Test
public void testAdd() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertThat(result).isEqualTo(5);
}
@Test
public void testSubtract() {
Calculator calculator = new Calculator();
int result = calculator.subtract(5, 3);
assertThat(result).isEqualTo(2);
}
}
反例:(未使用断言库):
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class CalculatorTest {
@Test
public void testAdd() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result);
}
@Test
public void testSubtract() {
Calculator calculator = new Calculator();
int result = calculator.subtract(5, 3);
assertEquals(2, result);
}
}
在正向例子中,使用了JUnit作为测试框架和AssertJ作为断言库。每个单元测试聚焦于测试一个特定的功能点(add和subtract方法),使用合适的测试数据进行测试,并通过断言来验证预期的结果。测试代码清晰、简洁,并且易于阅读和维护。
而在反向例子中,虽然也使用了JUnit作为测试框架,但未使用断言库,而是使用了JUnit提供的断言方法。这样的测试代码可读性较差,断言的错误信息也不够清晰,不利于定位问题和维护测试代码。
1.
功能测试
- 【强制】验证系统的用户界面(UI)功能。
说明:功能测试应该验证系统的用户界面功能是否符合预期,包括页面布局、交互操作、表单验证等。通过功能测试可以确保用户界面的可用性和易用性。
正例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class LoginPageTest {
@Test
public void testLoginPageLayout() {
LoginPage page = new LoginPage();
assertEquals("Login", page.getTitle());
assertTrue(page.isUsernameFieldDisplayed());
assertTrue(page.isPasswordFieldDisplayed());
assertTrue(page.isLoginButtonDisplayed());
}
@Test
public void testLoginWithValidCredentials() {
LoginPage page = new LoginPage();
page.setUsername("admin");
page.setPassword("password");
page.clickLoginButton();
assertTrue(page.isUserLoggedIn());
assertEquals("Welcome, admin!", page.getWelcomeMessage());
}
}
反例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class LoginPageTest {
@Test
public void testLoginPageLayout() {
LoginPage page = new LoginPage();
assertEquals("Login", page.getTitle());
assertTrue(page.isUsernameFieldDisplayed());
assertTrue(page.isPasswordFieldDisplayed());
assertFalse(page.isLoginButtonDisplayed());
}
@Test
public void testLoginWithValidCredentials() {
LoginPage page = new LoginPage();
page.setUsername("admin");
page.setPassword("password");
page.clickLoginButton();
assertFalse(page.isUserLoggedIn());
assertEquals("Invalid username or password", page.getErrorMessage());
}
}
- 【强制】验证系统的数据输入和输出。
说明:功能测试应该验证系统的数据输入和输出是否正确。包括验证输入数据的合法性、验证输出数据的准确性等。
正例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.\*;
public class CalculatorTest {
@Test
public void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result);
}
@Test
public void testDivision() {
Calculator calculator = new Calculator();
double result = calculator.divide(10, 2);
assertEquals(5.0, result);
}
}
反例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.\*;
public class CalculatorTest {
@Test
public void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(6, result);
}
@Test
public void testDivision() {
Calculator calculator = new Calculator();
double result = calculator.divide(10, 0);
assertEquals(Double.POSITIVE\_INFINITY, result);
}
}
- 【强制】针对系统的不同功能模块编写独立的测试用例。
说明:功能测试应该针对系统的不同功能模块编写独立的测试用例,以确保每个功能模块的正确性和稳定性。每个测试用例应该只验证一个功能模块,避免功能之间的耦合。
正例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.\*;
public class ShoppingCartTest {
@Test
public void testAddItemToCart() {
ShoppingCart cart = new ShoppingCart();
cart.addItem("item1", 10);
assertEquals(1, cart.getItemCount());
assertEquals(10, cart.getTotalPrice());
}
@Test
public void testRemoveItemFromCart() {
ShoppingCart cart = new ShoppingCart();
cart.addItem("item1", 10);
cart.removeItem("item1");
assertEquals(0, cart.getItemCount());
assertEquals(0, cart.getTotalPrice());
}
}
public class OrderProcessingTest {
@Test
public void testPlaceOrder() {
OrderProcessing orderProcessing = new OrderProcessing();
Order order = new Order();
order.addItem("item1", 10);
order.addItem("item2", 20);
orderProcessing.placeOrder(order);
assertTrue(order.isOrderPlaced());
assertEquals(30, order.getTotalPrice());
}
@Test
public void testCancelOrder() {
OrderProcessing orderProcessing = new OrderProcessing();
Order order = new Order();
order.addItem("item1", 10);
order.addItem("item2", 20);
orderProcessing.cancelOrder(order);
assertTrue(order.isOrderCancelled());
assertEquals(0, order.getTotalPrice());
}
}
反例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.\*;
public class ShoppingCartTest {
@Test
public void testAddItemToCart() {
ShoppingCart cart = new ShoppingCart();
cart.addItem("item1", 10);
assertEquals(1, cart.getItemCount());
assertEquals(10, cart.getTotalPrice());
}
@Test
public void testRemoveItemFromCart() {
ShoppingCart cart = new ShoppingCart();
cart.addItem("item1", 10);
cart.removeItem("item1");
assertEquals(1, cart.getItemCount()); _// __错误的断言_
assertEquals(0, cart.getTotalPrice());
}
}
public class OrderProcessingTest {
@Test
public void testPlaceOrder() {
OrderProcessing orderProcessing = new OrderProcessing();
Order order = new Order();
order.addItem("item1", 10);
order.addItem("item2", 20);
orderProcessing.placeOrder(order);
assertFalse(order.isOrderPlaced()); _// __错误的断言_
assertEquals(30, order.getTotalPrice());
}
@Test
public void testCancelOrder() {
OrderProcessing orderProcessing = new OrderProcessing();
Order order = new Order();
order.addItem("item1", 10);
order.addItem("item2", 20);
orderProcessing.cancelOrder(order);
assertFalse(order.isOrderCancelled()); _// __错误的断言_
assertEquals(0, order.getTotalPrice());
}
}
- 【强制】验证系统的权限和访问控制。
说明:功能测试应该验证系统的权限和访问控制是否正确。包括验证不同用户角色的访问权限、验证权限控制的正确性等。
正例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.\*;
public class AccessControlTest {
@Test
public void testAdminAccess() {
AccessControl accessControl = new AccessControl();
User admin = new User("admin", "admin");
assertTrue(accessControl.hasAccess(admin, "adminPage"));
assertTrue(accessControl.hasAccess(admin, "userManagement"));
}
@Test
public void testUserAccess() {
AccessControl accessControl = new AccessControl();
User user = new User("user", "password");
assertFalse(accessControl.hasAccess(user, "adminPage"));
assertTrue(accessControl.hasAccess(user, "userProfile"));
}
}
反例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.\*;
public class AccessControlTest {
@Test
public void testAdminAccess() {
AccessControl accessControl = new AccessControl();
User admin = new User("admin", "admin");
assertFalse(accessControl.hasAccess(admin, "adminPage")); _// __错误的断言_
assertTrue(accessControl.hasAccess(admin, "userManagement"));
}
@Test
public void testUserAccess() {
AccessControl accessControl = new AccessControl();
User user = new User("user", "password");
assertTrue(accessControl.hasAccess(user, "adminPage")); _// __错误的断言_
assertFalse(accessControl.hasAccess(user, "userProfile"));
}
}
- 【强制】验证系统的异常处理和错误处理。
说明:功能测试应该验证系统在面对异常情况和错误时的处理是否正确。包括验证系统是否能够正确地捕获和处理异常、是否能够给出合适的错误提示等。1,纯业务的(元模型定义好了,可自动化测试);1,幂等性测试;2,依赖项的测试(app要包含功能的依赖,数据的依赖,配置依赖)
正例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.\*;
public class ExceptionHandlingTest {
@Test
public void testDivideByZeroException() {
Calculator calculator = new Calculator();
assertThrows(ArithmeticException.class, () -\> {
calculator.divide(10, 0);
});
}
@Test
public void testInvalidInputException() {
Validator validator = new Validator();
assertThrows(InvalidInputException.class, () -\> {
validator.validateInput(null);
});
}
}
反例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.\*;
public class ExceptionHandlingTest {
@Test
public void testDivideByZeroException() {
Calculator calculator = new Calculator();
assertDoesNotThrow(() -\> {
calculator.divide(10, 0);
}); _// __错误的断言_
}
@Test
public void testInvalidInputException() {
Validator validator = new Validator();
assertDoesNotThrow(() -\> {
validator.validateInput(null);
}); _// __错误的断言_
}
}
- 【推荐】验证系统的性能和可扩展性。
说明:功能测试可以用于验证系统的性能和可扩展性。可以通过模拟并发用户、大数据量测试等方式来评估系统的性能和可扩展性。
正例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.\*;
public class PerformanceTest {
@Test
public void testConcurrentUsers() {
SystemUnderTest system = new SystemUnderTest();
int concurrentUsers = 100;
PerformanceResult result = system.runConcurrentUsersTest(concurrentUsers);
assertTrue(result.getAverageResponseTime() \< 500);
assertTrue(result.getThroughput() \> 100);
}
@Test
public void testLargeDataProcessing() {
SystemUnderTest system = new SystemUnderTest();
DataGenerator dataGenerator = new DataGenerator();
int dataSize = 1000000;
DataProcessingResult result = system.processLargeData(dataGenerator.generateData(dataSize));
assertEquals(dataSize, result.getProcessedDataCount());
assertTrue(result.getProcessingTime() \< 1000);
}
}
- 【推荐】验证系统的兼容性和互操作性。
说明:功能测试可以用于验证系统在不同平台、不同浏览器、不同设备上的兼容性和互操作性。可以通过跨浏览器测试、跨平台测试、API集成测试等方式来验证系统的兼容性和互操作性。
正例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.\*;
public class CompatibilityTest {
@Test
public void testCrossBrowserCompatibility() {
SystemUnderTest system = new SystemUnderTest();
Browser chrome = new Browser("Chrome");
Browser firefox = new Browser("Firefox");
CompatibilityResult result1 = system.testCompatibility(chrome);
CompatibilityResult result2 = system.testCompatibility(firefox);
assertTrue(result1.isCompatible());
assertTrue(result2.isCompatible());
}
@Test
public void testAPIIntegration() {
SystemUnderTest system = new SystemUnderTest();
API api = new API("https://api.example.com");
IntegrationResult result = system.testAPIIntegration(api);
assertTrue(result.isIntegrationSuccessful());
assertEquals(200, result.getResponseCode());
}
}
- 【推荐】定期运行测试并修复失败的测试。
说明:功能测试应该定期运行,并及时修复失败的测试用例。定期运行测试可以帮助发现系统的潜在问题,并及时采取措施进行修复。可以使用持续集成/持续交付(CI/CD)工具来定期运行功能测试,并在测试失败时发送通知给开发团队。开发团队应该及时分析失败的测试用例,并修复相关的问题。
这些是更多的功能测试规范和示例。这些规范可以帮助您在功能测试中提高测试质量和效率。请根据您的实际项目需求和情况,选择适合您团队的规范,并进行相应的调整和实现。
1.
集成测试
2.
性能测试
【推荐】性能测试是评估系统在不同负载条件下的性能表现的过程。为了确保性能测试的准确性和可重复性,需要遵循一套规范和最佳实践。
以下是性能测试规范的一些要点:
- 目标定义:明确性能测试的目标和需求。确定要测试的系统组件、功能或场景,以及期望的性能指标,如响应时间、吞吐量、并发用户数等。
- 测试环境:建立逼近真实生产环境的测试环境。包括硬件、网络、操作系统、数据库等方面的配置和设置。确保测试环境与生产环境的相似性,以便更准确地模拟实际情况。
- 测试数据:使用真实、多样化的测试数据进行性能测试。测试数据应该涵盖典型场景和边界情况,并具有一定的数据量和变化。
-
测试脚本和工具:编写清晰、可重复执行的性能测试脚本。选择适当的性能测试工具,如JMeter、LoadRunner等,用于执行和监控性能测试。
- 负载模拟:根据实际使用情况和预期负载,设计和模拟不同负载条件下的测试场景。包括正常负载、峰值负载、压力测试等。确保测试覆盖了系统的不同使用情况和负载情况。
- 测试监控和度量:监控系统在测试过程中的各项性能指标,如响应时间、CPU利用率、内存使用等。使用合适的监控工具和指标,以便及时发现性能瓶颈和问题。
- 结果分析和报告:对性能测试结果进行分析和总结。识别性能瓶颈、性能优化的潜力和建议等。生成详尽的测试报告,包括测试配置、执行过程、结果数据和结论。
- 迭代和持续测试:性能测试应该是一个迭代的过程,随着系统的演化和变化,不断进行性能测试和优化。同时,建立持续性能测试的机制,确保系统在每次发布和部署后的性能稳定性。
通过遵循性能测试规范,可以提高性能测试的准确性、可重复性和可靠性。这有助于发现和解决系统的性能问题,提升系统的性能和可扩展性,提供更好的用户体验。
1.
自动化测试
元模型自动化测试
自动化测试工具
1.
回归测试
- 【强制】所有代码必须进行单元测试,确保功能的正确性和稳定性。
说明:单元测试是保证代码质量的重要手段,通过编写针对各个模块和函数的测试用例,可以验证代码的正确性和稳定性。所有代码的提交前必须通过相应的单元测试。
正例:
_// __类:计算器_
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
_// __单元_测试_用例_
public class CalculatorTest {
@Test
public void testAdd() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3)); _// __正常情况_
assertEquals(-1, calculator.add(-1, 0)); _// __负数情况_
assertEquals(0, calculator.add(0, 0)); _// __边界情况,两个零相加_
}
}
反例:
_// __类:_计算器
public class Calculator {
public int add(int a, int b) {
return a - b;
}
}
_// __单元测_试_用例_
public class CalculatorTest {
@Test
public void testAdd() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3)); _// __错误的实现,应该返回 __ 2 - 3 = -1_
assertEquals(-1, calculator.add(-1, 0));
assertEquals(0, calculator.add(0, 0));
}
}
- 【强制】测试用例必须覆盖各种边界情况和异常情况。
说明:测试用例应该覆盖各种可能的输入和输出情况,包括边界值、异常情况、特殊字符等。通过充分的测试用例覆盖,可以发现潜在的问题和错误,提高代码的健壮性。
正例:
// __类:数组操作工具类
public class ArrayUtils {
public static int getMaxValue(int[] array) {
if (array.length == 0) {
throw new IllegalArgumentException("数组为空");
}
int max = array[0];
for (int i = 1; i \< array.length; i++) {
if (array[i] \> max) {
max = array[i];
}
}
return max;
}
}
_// __单元测试用例_
public class ArrayUtilsTest {
@Test
public void testGetMaxValue() {
int[] array1 = {1, 2, 3};
assertEquals(3, ArrayUtils.getMaxValue(array1)); _// __正常情况_
int[] array2 = {-1, -2, -3};
assertEquals(-1, ArrayUtils.getMaxValue(array2)); _// __负数情况_
int[] array3 = {1};
assertEquals(1, ArrayUtils.getMaxValue(array3)); _// __边界情况,只有一个元素_
int[] array4 = {};
try {
ArrayUtils.getMaxValue(array4); _// __边界情况,空数组_
fail("未抛出异常"); _// __未抛出异常,测试失败_
} catch (IllegalArgumentException e) {
_// __预期异常,测试通过_
}
}
}
反例:
// __类:数组操作工具类
public class ArrayUtils {
public static int getMaxValue(int[] array) {
int max = array[0];
for (int i = 1; i \< array.length; i++) {
if (array[i] \> max) {
max = array[i];
}
}
return max;
}
}
_// __单元测试用例_
public class ArrayUtilsTest {
@Test
public void testGetMaxValue() {
int[] array1 = {1, 2, 3};
assertEquals(3, ArrayUtils.getMaxValue(array1)); _// __正常情况_
int[] array2 = {-1, -2, -3};
assertEquals(-1, ArrayUtils.getMaxValue(array2)); _// __负数情况_
int[] array3 = {1};
assertEquals(1, ArrayUtils.getMaxValue(array3)); _// __边界情况,只有一个元素_
int[] array4 = {};
try {
ArrayUtils.getMaxValue(array4); _// __边界情况,空数组_
fail("未抛出异常"); _// __未抛出异常,测试失败_
} catch (ArrayIndexOutOfBoundsException e) {
_// __错误的实现,未处理空数组情况,抛出了数组越界异常_
}
}
}
- 【强制】提倡使用自动化测试工具进行测试。
说明:自动化测试工具可以提高测试效率和准确性,减少人工测试的工作量。常用的自动化测试工具包括JUnit、TestNG、Selenium等,根据具体需求选择合适的工具进行自动化测试。
- 【推荐】进行性能测试,确保系统在负载下的稳定性和性能表现。
说明:性能测试是评估系统在不同负载条件下的性能和稳定性的重要手段。通过模拟真实的负载情况,可以发现系统在高并发、大数据量等场景下的性能问题,并采取相应的优化措施。
- 【推荐】进行回归测试,确保代码修改不会对现有功能产生负面影响。
说明:回归测试是在代码发生变更后重新执行之前通过的测试用例,以确保修改代码不会破坏现有功能。回归测试可以防止由于代码修改引入的新错误,并保证系统的稳定性。
- 【推荐】对代码进行覆盖率评估,确保测试用例对代码的覆盖率达到预期。
说明:代码覆盖率评估是衡量测试用例对代码覆盖程度的指标。通过使用代码覆盖率工具,可以统计测试用例对代码的覆盖情况,帮助发现测试用例不足或冗余的情况,提高测试的有效性。
- 【推荐】编写清晰易懂的测试用例和文档,方便其他人理解和执行测试。
说明:良好的测试用例和文档可以提高测试的可维护性和可理解性。测试用例应该具备清晰的命名、详细的描述、预期结果等信息,文档应该包含测试环境的配置、测试步骤、预期结果等内容。
- 【推荐】进行安全性测试,确保系统的安全性和防御能力。
说明:安全性测试是评估系统在面对各种安全攻击和威胁时的表现和防御能力的测试活动。通过模拟常见的安全攻击场景,可以发现系统的安全漏洞和弱点,并采取相应的安全措施。
- 【推荐】进行用户体验测试,确保系统的易用性和用户满意度。
说明:用户体验测试是评估系统在用户角度下的易用性和用户满意度的测试活动。通过模拟真实用户的使用场景和操作行为,可以发现系统的界面设计、交互流程等方面的问题,并提供改进意见。
1.
授权管理规范
1.
平台授权
-
【强制】dev开发环境不需要授权,但Dev无法使用应用市场。
-
【强制】在启动iidp平台之前,必须进行授权操作。
说明:为了确保iidp平台的安全性和合规性,必须在启动之前进行授权操作。授权可以包括但不限于身份验证、访问权限控制等措施,以确保只有经过授权的用户可以访问和使用iidp平台。
根据上述新增规范,在启动iidp平台之前,必须进行授权操作,以确保平台的安全性和合规性。具体的授权方式可以根据实际情况进行选择和实施。
- 【强制】在启动iidp平台之前,必须进行授权操作。
说明:为了确保iidp平台的安全性和合规性,必须在启动之前进行授权操作。授权可以包括但不限于身份验证、访问权限控制等措施,以确保只有经过授权的用户可以访问和使用iidp平台。
1.
多租户
- 【强制】多租户授权
说明:每一个租户的创建都必须进行授权,包括租户能够使用的应用和数量,防止大规模使用多租户功能。
- 【强制】行列权限控制规范
说明:平台基于元模型进行行、列权限控制,在模型处理逻辑一致的情况下,可以通过权限控制实现千人千面效果。
1.
版本管理规范
1.
GIT使用规范
- 【建议】不建议使用rebase。
说明:虽然rebase能够使得提交的commit线很整洁,但这并不是实际的提交记录的真正反应,而且由于rebase会重新生成commit id,可能会导致很多冲突的情况。
- 【强制】使用版本管理工具Git进行代码版本管理。
说明:Git是一种分布式版本控制系统,可以有效地管理代码的版本和变更历史。通过使用Git,可以轻松地进行代码的协作开发、版本回退、分支管理等操作,提高团队协作效率和代码质量。
- 【强制】使用合适的分支策略进行代码开发和管理。
说明:合适的分支策略可以有效地组织代码的开发和管理流程,常见的分支策略包括主分支(master/main)、开发分支(develop)、特性分支(feature)、发布分支(release)、修复分支(hotfix)等。根据具体项目和团队的需求,选择合适的分支策略进行代码管理。
- 【强制】提交代码前进行代码审查。
说明:代码审查是保证代码质量和一致性的重要环节。通过代码审查,可以发现潜在的问题和错误,提高代码的可读性和可维护性。在提交代码之前,应邀请其他团队成员进行代码审查,并根据审查结果进行相应的修改和优化。
- 【强制】使用有意义的提交消息。
说明:提交消息是对代码变更的简要描述,应该清晰、有意义且符合规范。提交消息应该包含变更的目的、内容和影响等信息,方便其他团队成员理解和追踪代码变更历史。
正例:
feat: 添加用户注册功能
添加了用户注册功能,包括表单验证、数据存储和页面跳转等功能。
反例:
fix: 修复bug
修复了一个bug。
- 【强制】遵循代码合并流程,确保代码的一致性和稳定性。
说明:代码合并是将不同分支上的代码合并到一起的过程,应该遵循合适的合并流程,包括解决冲突、测试合并后的代码等步骤,确保合并后的代码一致性和稳定性。
- 【强制】使用标签管理发布版本。
说明:标签是对特定版本的代码进行命名和标记,方便追踪和发布。在每次发布稳定版本时,应该创建相应的标签,并注明版本号和发布日期等信息。
- 【建议】使用Git Hooks进行代码质量检查。
说明:Git Hooks是Git提供的钩子机制,可以在特定的Git操作触发相应的脚本。通过使用Git Hooks,可以在代码提交、合并等操作前进行代码质量检查,例如代码格式化、静态代码分析等,提高代码的质量和一致性。
- 【建议】使用Git Flow等工具辅助分支管理。
说明:Git Flow是一种流行的Git分支管理工具,可以简化分支管理流程,提供命令行工具和图形界面工具来支持分支的创建、合并、发布等操作。使用Git Flow等工具可以提高分支管理的效率和可靠性。
- 【建议】定期进行代码仓库的维护和清理。
说明:定期进行代码仓库的维护和清理可以减少仓库的冗余和混乱,提高代码仓库的性能和可用性。包括删除不再需要的分支、清理过期的标签、整理和优化仓库结构等操作。
- 【建议】使用Git相关的工具和服务进行代码托管和协作开发。
说明:Git相关的工具和服务提供了丰富的功能和便捷的操作,可以方便地进行代码托管、协作开发、问题追踪等工作。常见的Git工具和服务包括GitHub、GitLab、Bitbucket等,根据团队的需求选择合适的工具和服务进行使用。
以上是使用Git的一些常见规范,包括分支管理、代码审查、提交消息、合并流程等方面根据具体项目和团队的需求,可以进行相应的调整和补充。
1.
CICD规范
- 【强制】 使用CI/CD工具进行自动化的代码构建、测试和部署。
说明:CI/CD(持续集成/持续部署)是一种软件开发实践,通过自动化的流程来构建、测试和部署代码,以提高开发效率和软件质量。选择合适的CI/CD工具(如Jenkins、GitLab CI、Travis CI等)来配置和管理CI/CD流程,确保代码的自动化构建、测试和部署。
- 【强制】 将CI/CD配置文件纳入版本控制。
说明:CI/CD配置文件是定义CI/CD流程的文件,应该将其纳入版本控制,与代码一起进行管理。这样可以确保CI/CD配置与代码版本的一致性,方便团队成员查看和修改CI/CD配置。
- 【强制】 在CI/CD流程中包含代码编译、单元测试和静态代码分析等步骤。
说明:CI/CD流程应该包含代码的编译、单元测试和静态代码分析等步骤,以确保代码的质量和稳定性。在编译阶段,将源代码编译成可执行的程序或库;在单元测试阶段,运行针对代码的单元测试,验证代码的正确性;在静态代码分析阶段,使用工具对代码进行静态分析,检查潜在的问题和代码质量。
- 【强制】 使用环境变量管理敏感信息。
说明:敏感信息(如数据库密码、API密钥等)应该通过环境变量进行管理,而不应直接写入代码或配置文件中。在CI/CD流程中,使用环境变量来获取敏感信息,并确保在不同环境中使用不同的敏感信息。
- 【强制】 在CI/CD流程中进行集成测试和部署到测试环境。
说明:在CI/CD流程的后续阶段,应该进行集成测试和部署到测试环境。集成测试是对不同模块或服务的集成进行测试,验证系统的整体功能和兼容性;部署到测试环境是将代码部署到与生产环境相似的测试环境中,以进行更全面的测试和验证。
- 【强制】 使用持续集成服务器进行自动化构建和测试。
说明:持续集成服务器是用于执行CI/CD流程的服务器,可以自动触发代码构建、测试和部署。通过配置持续集成服务器,可以实现代码的自动化构建和测试,提高开发效率和代码质量。
- 【强制】 使用容器化技术进行部署。
说明:容器化技术(如Docker)可以将应用程序及其依赖项打包成独立的容器,提供了一致的运行环境,方便部署和扩展。在CI/CD流程中,可以使用容器化技术将应用程序打包成镜像,并在部署阶段使用这些镜像进行快速部署和回滚。
- 【建议】 使用自动化测试工具进行端到端测试。
说明:自动化测试工具可以模拟用户的行为,对整个应用程序进行端到端的测试,以验证系统的功能和性能。在CI/CD流程中,可以使用自动化测试工具进行端到端测试,提高测试的覆盖范围和准确性。
- 【建议】 使用日志和监控工具进行应用程序的监控和故障排查。
说明:日志和监控工具可以帮助监控应用程序的运行状态和性能指标,并及时发现和排查故障。在CI/CD流程中,可以配置日志和监控工具,以便在部署后及时获取应用程序的运行情况,并进行故障排查和优化。
- 【建议】 定期审查和优化CI/CD流程。
说明:CI/CD流程是一个持续演进的过程,应该定期审查和优化流程,以适应项目的需求和变化。通过审查和优化CI/CD流程,可以提高开发效率和代码质量,减少部署和发布的风险。
1.
发版规范
2.
APP版本规范
- App的名称,版本,描述,产品线(分类,业务领域,不好约定,业务行为)
- 行业,领域,分类的方式体现
1.
工程结构规范
1.
APP设计规范
- 【推荐】app的拆分推荐考虑基于商业角度、复用性、职责、性能、部署场景等要素综合考虑后进行拆分,业务的变化是复杂的,要基于具体的业务场景来进行设计,这点非常非常重要,关系到后续的具体业务开发的方向。
说明:具体app的拆分规范主要包括以下几个方面:
- 对app进行分层
app层级越低则通用性越强,层级越高则有更多的通用app选择,可以拿来即用或通过扩展使用。随着app层级清晰且越来越多,可以逐步形成app货架:
- L1:平台通用app
包括运维、权限、中间件集成、api集成、数据流处理等相关的app
- L2:业务通用app
包括通知、告警、文件、字典、审批流、打印、主数据等业务通用app
- L3:产品通用app
包括Iot、smi、qms等产品通用app
- L4:行业通用app
包括电子套件、pcb、光伏等行业app
- L5:定制app
包括各类企业定制的app
- 按商业模式划分app
哪些应用、模块、功能是打算独立销售的,可以将这些功能封装到独立app中。
- 按耦合程度划分app
可参考DDD,识别领域模型、聚合根等,将高内聚的模型封装在一个app。
- 识别并分离不变与变化app
将不变的或很少变化的模型封装在一个app,将变化频繁的模型、或扩展模型封装在另外的app。
- 识别高并发场景
将对性能要求非常苛刻的模型封装到独立app中。
- 【推荐】app调用关系规范
说明:appA方法或服务调用到另外一个appB的方法或服务,这是调用关系:
- 调用关系相关方app具备安装顺序无关性;
-
调用关系会影响到运行时的方法或服务调用,如指定appB的方法或服务找不到.
- 【推荐】app依赖关系规范
说明:appA与另外appB的属性之间具备ER关系,或模型之间具有继承、扩展关系,这是依赖关系,我们应梳理好依赖关系后再进行建模,以免导致循环依赖:
- A继承/扩展了B的模型,必须要先装B,再安装A;
-
A与B的模型之间可能存在1:N、N:1、N:N三种ER关系,N:1与N:N支持单边关系,
- 【推荐】单边关系与双边关系使用规范
说明:可以独立安装,1:N不支持单边关系需要N方先安装,因此在使用1:N时需要尽量避免循环依赖。平台支持N:1、N:N的单边关系,特别适用于跨App扩展ER关系的 场景,在保证不改动原App的情况下,扩展ER关系:
- 双边关系
模型双方建立ER关系,可以双向获取对端的数据
- 单边关系
模型A建立关联模型B的ER关系,B不需要配置与A的关系,适用于通过A获取B,但是不需要通过B获取A。如:业务模型关联码表,码表不需要关联业务模型。
- 【推荐】跨模型方法调用规范
说明:平台为了保证扩展能力,所有的模型、属性、服务等都是可以动态扩展的,因此 本质上每个元模型都是Map,而不是具体的Class:
- 不建议在平台中用new模型的方式创建对象
- 调用方法也应该采用统一的call方法
- get set方法建议采用平台提供的模板(基于idea模版)
1.
pom引入规范
- 【强制】禁止引入hutool包。
- 第三方的包,谨慎引入,防止app打包体积过大
- pom编写规范
<?xml>
......
<dependencies>
\<!_-- springboot --\> _
\<dependency\>
\<groupId\>org.springframework.boot\</groupId\>
\<artifactId\>spring-boot-starter-web\</artifactId\>
\<version\>2.1.8.RELEASE\</version\>
\</dependency\>
\<!_-- __引擎 __ --\> _
\<dependency\>
\<groupId\>com.sie.meta\</groupId\>
\<artifactId\>sie-iidp-engine\</artifactId\>
\<version\>1.0-SNAPSHOT\</version\>
\<scope\>compile\</scope\>
\</dependency\>
\<!_-- __脚本引擎(可选) __ --\>_
\<dependency\>
\<groupId\>com.sie.meta\</groupId\>
\<artifactId\>sie-iidp-script-engine\</artifactId\>
\<version\>1.0-SNAPSHOT\</version\>
\</dependency\>
\</dependencies\>
\<build\>
\<plugins\>
\<plugin\>
\<groupId\>org.springframework.boot\</groupId\>
\<artifactId\>spring-boot-maven-plugin\</artifactId\>
\<version\>2.7.2\</version\>
\<executions\>
\<execution\>
\<goals\>
\<goal\>repackage\</goal\>
\</goals\>
\</execution\>
\</executions\>
\</plugin\>
\<plugin\>
\<groupId\>org.apache.maven.plugins\</groupId\>
\<artifactId\>maven-compiler-plugin\</artifactId\>
\<version\>3.3\</version\>
\<configuration\>
\<source\>1.8\</source\>
\<target\>1.8\</target\>
\<compilerArgs\>
\<arg\>-parameters\</arg\>
\</compilerArgs\>
\</configuration\>
\</plugin\>
\</plugins\>
\</build\>
</xml>
1.
工程结构规范
代码目录结构,怎么安排,命名,约定大于配置
Model、app.json、views、service 必须在同一级目录
Views里面的文件内容字段命名和model里面的字段有关系。
字符串关联,编译插件来识别并提示。
运行阶段,提示字符串相关问题,可以考虑通过静态分析来进行处理。
由于引擎是一个jar包,在开发环境中,通过一个引擎jar包来加载正在开发的apps,对于调试非常不友好,建议通过新建一个server项目依赖引擎jar包,以及必要的配置(如端口配置)来启动引擎,加载正在开发的apps进行调试。
server启动时,会默认加载apps目录下在apps.json中配置的所有app jar包;iidp-demo-apps是我们需要开发的app工程,一般来说我们现在都分多个工程进行开发,可以在iidp-demo-apps下面建多个module。
{
"loaders": {
"API": "com.sie.iidp.engine.loaders.ApiLoader",
"SDK": "com.sie.iidp.loaders.SdkLoader"
},
"apps": {
"master": {
"SDK": {
"base": "sie-iidp-base-1.0-SNAPSHOT.jar",
"app名称: "jar包名称"
}
}
}
}
apps.json文件是引擎必须的文件,默认在${user.dir}/apps/apps.json,需要把app打包生成的jar放置在此,并按照上面的格式配置。sie-iidp-base-1.0-SNAPSHOT.jar由引擎提供,开发阶段可从平台处获得。
1.
公共错误码规范
【推荐】在开发和测试过程中,定义和使用一套统一的公共错误码规范可以提高代码的可读性、可维护性和可测试性。公共错误码规范定义了一系列标准错误码及其对应的错误信息,使得开发人员和测试人员能够快速理解和处理错误情况。
具体要求和建议如下:
- 错误码命名规范:定义一套统一的错误码命名规范,例如使用大写字母和下划线分隔的形式,例如:INVALID_PARAMETER。
- 错误码分类:根据错误类型或模块进行分类,例如将参数相关的错误码放在一个范围内,将数据库相关的错误码放在另一个范围内。这样可以更好地组织和管理错误码。
- 错误码取值范围:为每个错误码定义一个取值范围,以便后续的扩展和维护。例如,可以为参数相关的错误码分配范围为 1000-1999,数据库相关的错误码分配范围为 2000-2999。
- 错误码文档化:为每个错误码提供清晰的错误信息和解释,以便开发人员和测试人员能够快速理解错误的含义和处理方式。这些信息可以在代码注释、文档或错误码定义文件中进行记录。
- 错误码使用范围:明确错误码的使用范围,例如哪些模块或功能应该使用哪些错误码。这样可以避免错误码的混乱和重复使用。
- 错误码的处理和返回:在代码中正确处理错误码,并根据错误码返回适当的错误信息给用户或调用方。错误信息应该清晰、准确地描述错误的原因,帮助用户或调用方理解和解决问题。
通过制定和遵守公共错误码规范,可以提高代码的可维护性和可测试性,减少错误处理的复杂性,并提供更好的用户体验。同时,还能够促进团队之间的协作和沟通,减少因错误处理不一致而引起的问题。
1.
部署规范
1.
前端部署
- 【强制】获取真实IP时,需要在Nginx配置中添加以下配置项:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
说明:在使用Nginx作为反向代理时,为了获取客户端的真实IP地址,需要在Nginx的配置文件中添加上述配置项。这样,Nginx会将客户端的真实IP地址添加到请求头的X-Forwarded-For字段中,以便后端服务器获取到真实IP地址。 正例:在Nginx配置文件中添加如下配置项:
location / {
proxy_pass http://backend;
proxy_set_header X-Real-IP $remote_addr;
proxy\_set\_header X-Forwarded-For $proxy\_add\_x\_forwarded\_for;
}
反例:未添加上述配置项,导致后端服务器无法获取到真实IP地址。
根据上述新增规范,应在Nginx的配置文件中添加配置项proxy_set_header X-Real-IP remote_addr 和 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for,以确保能够获取到客户端的真实IP地址。
1.
后端部署
- 【强制】部署模式,确定是单机版部署还是分布式模式部署。
说明:由配置文件中的engine.run.mode=SINGLE 配置项确定。
- 【推荐】用linux,不要用windows
说明:在开发和部署过程中,选择适合的服务器操作系统对于项目的稳定性和性能至关重要。Linux 作为一种开源操作系统,具有广泛的支持和强大的性能优势,尤其在服务器领域表现出色。相比之下,Windows 服务器在某些方面可能不如 Linux 服务器稳定和高效。
正例:使用 Linux 服务器可以获得更好的性能和稳定性。它提供了强大的命令行工具和灵活的配置选项,适用于各种服务器应用和开发环境。
反例:使用 Windows 服务器可能会面临一些限制和性能瓶颈。Windows 操作系统相对较重,可能需要更多的系统资源,并且在某些情况下可能不够稳定。
请注意,选择适合的服务器操作系统应根据具体项目需求和技术栈来决定。在某些特定情况下,使用 Windows 服务器可能是必要的,比如需要与 Microsoft 技术栈紧密集成的项目。然而,总体而言,推荐使用 Linux 服务器可以获得更好的性能和稳定性。
- 【强制】开发环境和部署环境要保持一致
说明:为了确保代码在不同环境中的一致性和可靠性,开发环境和部署环境应该尽可能保持一致。这包括操作系统、软件版本、配置文件等方面的一致性。
正例:在开发过程中,使用与目标部署环境相同的操作系统和软件版本进行开发和测试。确保开发团队和部署团队使用相同的工具链和依赖项,以减少环境差异可能带来的问题。
反例:在开发过程中使用不同于目标部署环境的操作系统或软件版本,导致在部署时出现兼容性问题或意外的行为差异。
通过保持开发环境和部署环境的一致性,可以最大程度地减少因环境差异而引起的问题。这样可以更好地预测和管理应用程序的行为,并提高部署过程的可靠性和效率。同时,也方便开发团队和运维团队之间的协作和沟通,减少因环境差异而导致的沟通和排查问题的成本。
需要注意的是,有时候在开发环境和部署环境之间可能存在一些差异,比如数据库的配置、外部服务的依赖等。在这种情况下,应该及时通知和协调相关团队,确保环境差异被妥善处理,并进行必要的测试和验证,以确保代码在部署环境中的正常运行。