最終更新 1 week ago

Add_tests.md Raw

加测试

这个仓库是一个 .NET 仓库。显然你不难理解它的项目结构,你可以先简单阅读一下它的代码。

它已经有完善的入口点、测试项目等。但是,目前社区普遍批评这个仓库的代码覆盖率很低。尤其是很多核心业务并没有被测试覆盖。

现在你需要扮演一位高级 .NET 专家,试图提高这个仓库的代码覆盖率。你可以直接去测试项目里增加新的 UT 。

注意,你没必要mock很多东西,例如数据库、文件系统等。它们在测试环境完全准备的也非常妥当了。你的测试大可以直接作为集成测试,也就是直接启动Web项目,调用它的API,检查返回效果。

被测试的项目中的用例,例如对外部仓库的访问,你也大可以直接让它真的去访问即可。除非是非常昂贵的请求,我们需要Mock。

Mock的方法就是在TestStartup里,对一个Service进行继承,override掉老方法,然后再把新服务替代老服务注册回去。

这样整个测试结构非常清晰。现在,你来试图开始增加几个新的测试类,覆盖一些核心功能吧!

如果老的代码实在有函数很难覆盖,考虑增加 [ExcludeFromCodeCoverage] 吧!

在写完测试以后,你可以使用 dotnet test 命令来执行你的测试。如果执行失败,除非你非常确定是真的源码业务逻辑有错误,否则不要修改源码。

Fix_lint.md Raw

修Lint炸

这是一个 .NET 仓库,它使用了 jb 来进行lint。当然,你可以直接运行其根目录下的 lint.sh 文件,以得到lint的结论。它可能无法通过lint。你会从STDOUT里阅读出无法通过的理由。

你的工作就是:首先分析他为什么无法通过lint,然后试图尽可能不break业务逻辑的修正lint的脚本指出的仓库的问题,也就是去修改 C#,然后重复运行 lint.sh 直到全部通过。

你不得修改 lint.sh 文件也不必要分析它!因为这个文件是用来lint的。如果它发现了错误,一定是仓库的错误,不是 linter 的!直接执行即可得到仓库本身的lint错误信息。

当然,最好你也跑一次 dotnet build 和 dotnet test,确保可以编译、UT全过。

尽可能最小化修改。我们的修改会被社区review。所以只修改必要的地方。不要添加无意义的注释或格式化。尽量不添加新包,不要改变现有的语法风格。

最后 git commit 并 git push 即可。message你可以自己写。从而帮助我修正这个仓库的问题。

fix_entity.md Raw

Aiursoft 有一套自己的 Entity Framework Core 实体建模规范。

这套规范非常严格,内容如下:

Aiursoft Entity Framework Core 实体建模规范

核心哲学:独裁模式 (Dictator Mode)

  1. DbSet 独裁:数据的增删改(CUD)必须通过 DbContext.DbSet<T> 进行。
  2. 显式加载:禁止任何形式的隐式行为(如延迟加载),数据必须显式 Include
  3. 编译期契约:利用 C# 类型系统(Type System)在编译期暴露潜在错误,而不是推迟到运行期。
  4. 表格可见: 所有实体类必须清晰地表达其数据列和关系,避免隐式约定。所有实体类必须出现在 DbContext 中的 public DbSet<Repo> Repos => Set<Repo>();,表名必须是实体类名的复数形式(如 User 对应 Users 表)。

1. 主键规范 (Primary Keys)

1.1 类型限制 所有实体类的主键必须是 intGuid 类型。

  • 例外:直接继承自 Microsoft.AspNetCore.Identity.IdentityUser 的实体除外(默认使用 string,允许保持原样)。

1.2 强制特性 所有实体类的主键必须被 [Key] Attribute 修饰。

1.3 不可变性 所有实体类的主键必须是 { get; init; }(创建后不可变)。

示例:

[Key]
public Guid Id { get; init; }

2. 外键与导航规范 (Foreign Keys & Navigation)

2.1 成对定义 所有外键关系必须显式定义两个属性:外键ID导航引用

2.2 外键ID 必填性

  • 必填关系:外键ID 必须是 Guid (或 int),且属性必须是 required
  • 可选关系:外键ID 必须是 Guid? (或 int?),且属性必须是 required (强制显式赋值 null)。

2.3 导航引用可空性 所有导航引用属性必须声明为可空类型(如 User?)。

  • 理由:在 new Entity() 时我们通常只赋值 ID 而不赋值对象,声明为可空是为了满足 C# 编译器的初始化检查,避免虚假的警告。

2.4 导航属性必须 NotNull 所有导航引用属性必须被 [NotNull] 修饰。

2.5 序列化屏蔽 所有导航引用属性必须被 [Newtonsoft.Json.JsonIgnore] 修饰(防止死循环)。

2.6 禁止 Virtual (禁止延迟加载) 所有导航属性严禁使用 virtual 关键字。

  • 后果:彻底禁用 Lazy Loading Proxy。
  • 操作要求:查询关联数据时,必须显式调用 .Include().ThenInclude()

示例:

// 外键 ID (必填)
public required Guid UserId { get; set; }

// 导航引用 (必须可空,必须 NotNull,禁止 virtual)
[JsonIgnore]
[ForeignKey(nameof(UserId))]
[NotNull]
public User? User { get; set; }

3. 集合与独裁规范 (Collections & Dictatorship)

3.1 类型限制 所有反向导航集合必须声明为 IEnumerable<T> 类型。

  • 目的:在编译层面阉割集合的 .Add().Remove() 方法。

3.2 初始化 所有集合属性必须初始化为 new List<T>() 以防止空引用异常。

3.3 显式关联 所有集合属性必须被 [InverseProperty] 修饰。

3.4 独裁修改原则 禁止将 IEnumerable 强转为 List 来操作数据。

  • 增加子项:必须创建新对象并 _dbContext.Add(newItem)
  • 删除子项:必须查询出对象并 _dbContext.Remove(item)

示例:

[InverseProperty(nameof(ExamPaperSubmission.User))]
public IEnumerable<ExamPaperSubmission> Submissions { get; init; } = new List<ExamPaperSubmission>();

4. 数据列规范 (Data Columns)

4.1 长度约束 所有 stringbyte[] 类型的列必须被 [MaxLength] 约束。

4.2 空值语义

  • 不可空列:使用非空类型(如 string),严禁使用 [NotNull] 修饰可空类型。
  • 可空列:使用可空类型(如 string?),且必须在 XML 注释中说明为空代表什么业务状态

4.3 不可变属性 所有业务上不可编辑的属性(如 CreationTime),必须是 { get; init; }


5. 完整标准模板 (V3.0)

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis; // 尽量少用,除非用于辅助方法
using Newtonsoft.Json;

namespace Aiursoft.Exam.Entities;

public class TemplateEntity
{
    // [规则 1.1, 1.2, 1.3] 主键:Guid/int, Key, init
    [Key]
    public Guid Id { get; init; }

    // [规则 4.1, 4.2] 必填列:required string, MaxLength, 禁止 [NotNull]
    [MaxLength(100)]
    public required string Name { get; set; }

    // [规则 4.2, 4.3] 可空列:string?, 注释说明含义, init (如果是创建时不变量)
    /// <summary>
    /// 描述信息。
    /// 若为空,表示用户在创建时未填写备注。
    /// </summary>
    [MaxLength(1000)]
    public string? Description { get; init; }

    // [规则 4.3] 系统字段
    public DateTime CreationTime { get; init; } = DateTime.UtcNow;

    // ================= 关联关系 =================

    // [规则 2.2] 外键ID:required (即使是 Guid? 也要 required 以强制显式赋值)
    public required Guid ParentId { get; set; }

    // [规则 2.3, 2.4, 2.5, 2.6] 
    // 导航引用:Type?, JsonIgnore, ForeignKey, NotNull
    // 严禁 virtual (禁用延迟加载)
    [JsonIgnore]
    [ForeignKey(nameof(ParentId))]
    [NotNull]
    public ParentEntity? Parent { get; set; }

    // [规则 3.1, 3.2, 3.3] 
    // 集合:IEnumerable (独裁模式), InverseProperty, new List()
    // 严禁 virtual
    [InverseProperty(nameof(ChildEntity.Parent))]
    public IEnumerable<ChildEntity> Children { get; init; } = new List<ChildEntity>();
}

下面,你正在修正项目的代码,确保其符合严格的实体建模规范。

你需要小心谨慎的梳理实体,考虑已经存住于数据库中的数据,修改时要避免break已有的业务逻辑或API。仔细检查一些东西是不是by design或必须打破军规的。

如果你发现明显的疏漏、错误,你可以去修正实体类。务必小心!实体类是整个项目的最底层,直接修正可能牵一发动全身,小心行事!

如果你完成了变更,你必须对每种数据库类型都创建 migration。例如:

cd ./src/MyOrg.MarkToHtml.Sqlite/
dotnet ef migrations add AddMarkdownDocumentsTable --context "SqliteContext" -s ../MyOrg.MarkToHtml/MyOrg.MarkToHtml.csproj
cd ..

为 MySQl 创建迁移的时候,必须先改好 appsettings.json:

{
  "ConnectionStrings": {
    "AllowCache": "True",
    "DbType": "MySql",
    "DefaultConnection": "Server=localhost;Database=template;Uid=template;Pwd=template_password;"
  },
  // ... other settings ...
}

启动容器:

sudo docker run -d --name db -e MYSQL_RANDOM_ROOT_PASSWORD=true -e MYSQL_DATABASE=template -e MYSQL_USER=template -e MYSQL_PASSWORD=template_password -p 3306:3306 mysql

然后才能

cd ./src/MyOrg.MarkToHtml.MySql/
dotnet ef migrations add AddMarkdownDocumentsTable --context "MySqlContext" -s ../MyOrg.MarkToHtml/MyOrg.MarkToHtml.csproj

小心操作!现在开始扮演你的实体判官,去修正当前项目吧!

fix_tests.md Raw

修测试炸

这个仓库是一个 .NET 仓库。显然你不难理解它的项目结构,你可以先简单阅读一下它的代码。

它已经有完善的入口点、测试项目等。但是,目前社区普遍批评这个仓库的代码测试通过率很低,经常测试失败。

现在你需要扮演一位高级 .NET 专家,试图判断代码测试失败时,是测试用例的问题,还是环境的问题,还是真的发现了业务逻辑错误。

注意,你没必要mock很多东西,例如数据库、文件系统等。它们在测试环境完全准备的也非常妥当了。你的测试大可以直接作为集成测试,也就是直接启动Web项目,调用它的API,检查返回效果。

被测试的项目中的用例,例如对外部仓库的访问,你也大可以直接让它真的去访问即可。除非是非常昂贵的请求,我们需要Mock。

Mock的方法就是在TestStartup里,对一个Service进行继承,override掉老方法,然后再把新服务替代老服务注册回去。

这样整个测试结构非常清晰。现在,你来试图开始运行 dotnet test 得到测试结果,然后分析测试内容,讨论为什么失败,判断是真的bug还是用例问题,尝试缓解。

在写完测试以后,你可以使用 dotnet test 命令来执行你的测试。如果执行失败,除非你非常确定是真的源码业务逻辑有错误,否则不要修改源码。

strict.md Raw

严格化

我受够了你的代码了!你为了快速实现功能,选择了非常糙快猛的方法。

你的各种Helper,像极了初级程序员不会OO,把一样的代码腾了个函数。很多对象本身的行为你不会抽象!

很多复杂的验证逻辑你不会封装,封装出来的函数名字极其困惑我都不知道俩有什么区别。

而且你不喜欢抛错。我猜你可能是为了拟真真实的生产环境容忍噪音,但是你suppress错误我是完全不理解。现在在开发阶段,什么都应该严格着来。

你要赶紧反思,不要用现在这种遇到错误就suppress、遇到feature就糊一段的方式写代码;

而是仔细思考尽量让每一层都透明、都函数式、都面向对象,都封装好。

别人直接调用你的类,就能完成它的设计功能,而内部处理好安全、验证。

你做不到吗?反思,检讨、仔细设计,然后重构!代码要像基类一样暴露出来的api非常简明清晰!