WEBSITE ĐANG PHÁT TRIỂN

Cursor + .NET: 6 senior rules team BKGlobal dùng để giữ AI agent không phá code production

Cursor + .NET: 6 senior rules team BKGlobal dùng để giữ AI agent không phá code production

Gần đây team BKGlobal onboard Cursor cho vài backend .NET projects. Trải nghiệm ban đầu khá thất vọng , không phải vì Cursor yếu, mà vì AI agent thiếu những kiến thức senior dev đã burn-in qua nhiều năm. Một bài digest mới trên dev.to từ Agentic Architect tổng hợp 6 rules dạng "điều AI hay làm sai trong code C#/.NET và cách bắt lại". Bài này là reaction của team , chúng tôi đã verify từng rule trong codebase thật và thêm vài kinh nghiệm BKGlobal.

Rule 24 , Chỉ dùng ValueTask khi có lý do

ValueTask là một perf optimization cho hot paths thường xuất hiện synchronously. Nó không phải drop-in replacement cho Task. Cursor swap chúng qua lại mà không suy nghĩ.

Cách bắt lỗi: Flag bất kỳ return type nào là ValueTask và hỏi: method này có thực sự mostly synchronous không? Nếu không, revert về Task.

// Cursor thường generate
public ValueTask<Order> GetCachedOrderAsync(int id)
    => _cache.TryGetValue(id, out var o)
        ? new ValueTask<Order>(o)
        : new ValueTask<Order>(await LoadFromDb(id));

// Senior rule
public async Task<Order> GetCachedOrderAsync(int id)
    => _cache.TryGetValue(id, out var o)
        ? o
        : await LoadFromDb(id);

Lý do: ValueTask chỉ nên dùng ở hot path synchronous cache hit. Mọi code path có await thì ValueTask chỉ thêm overhead vì phải wrap thành Task nội bộ.

Rule 23 , Không bool flag parameters

SendEmail(string to, bool isHtml) nên được refactor thành SendHtmlEmail và SendPlainEmail. Bool flags giấu branching mà nên thuộc về type system.

Cách bắt lỗi: Flag bất kỳ method signature có hai hoặc nhiều bool parameters là refactor candidate.

// Cursor thường generate
public Task SaveUserAsync(User u, bool isAdmin, bool isVerified) { ... }

// Senior rule
public abstract record UserRole;
public record AdminUser(User Inner) : UserRole;
public record VerifiedUser(User Inner) : UserRole;
public record RegularUser(User Inner) : UserRole;

public Task SaveUserAsync(UserRole role, User u) { ... }

Lý do: khi signature có nhiều bool flags, khả năng cao là bạn đang thiếu một abstraction. Phân tách thành các method riêng biệt (hoặc type hierarchy) làm method signature tự giải thích.

Rule 22 , BackgroundService thay vì Task.Run

Long-running work trong ASP.NET Core phải đi vào BackgroundService, không phải Task.Run trong controller. Cursor sẽ sẵn sàng gọi Task.Run và gọi nó là "async work" , request thread của bạn sẽ chết giữa chừng và bạn không bao giờ biết tại sao.

Cách bắt lỗi: Catch Task.Run ngoài test code và propose hosted service.

// Cursor thường generate ,  sai
public class ReportController : ControllerBase
{
    [HttpPost("generate")]
    public IActionResult Generate(ReportRequest req)
    {
        Task.Run(() => BuildReport(req)); // request thread sẽ die
        return Accepted();
    }
}

// Senior rule ,  đúng
public class ReportGenerationService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        await foreach (var req in _channel.Reader.ReadAllAsync(ct))
        {
            await BuildReportAsync(req, ct);
        }
    }
}

Lý do: Task.Run fire-and-forget không có cancellation, không có logging khi exception, và request thread không theo dõi được. BackgroundService tích hợp với ASP.NET Core shutdown, có cancellation token, có logging.

Rule 21 , Channels cho Producer-Consumer

System.Threading.Channels thắng BlockingCollection và thắng roll-your-own queue cộng SemaphoreSlim. AI với tới ConcurrentQueue mỗi lần và tự stitch lại bằng tay. Một rule detect producer/consumer patterns và propose Channel sẽ save bạn một class.

// Cursor thường generate ,  sai
public class OrderQueue
{
    private readonly ConcurrentQueue<Order> _orders = new();
    private readonly SemaphoreSlim _signal = new(0);

    public async Task EnqueueAsync(Order o)
    {
        _orders.Enqueue(o);
        _signal.Release();
    }

    public async Task<Order> DequeueAsync(CancellationToken ct)
    {
        await _signal.WaitAsync(ct);
        _orders.TryDequeue(out var o);
        return o!;
    }
}

// Senior rule ,  đúng
public class OrderChannel
{
    private readonly Channel<Order> _channel = Channel.CreateUnbounded<Order>();

    public async ValueTask EnqueueAsync(Order o, CancellationToken ct = default)
        => await _channel.Writer.WriteAsync(o, ct);

    public IAsyncEnumerable<Order> DequeueAllAsync(CancellationToken ct = default)
        => _channel.Reader.ReadAllAsync(ct);
}

Lý do: Channel đã bao gồm cancellation, completion signaling, bounded/unbounded options, và được test kỹ bởi .NET team. Tự build trên ConcurrentQueue + SemaphoreSlim thường miss edge cases (poison messages, back-pressure, completion).

Rule 19 , NetArchTest cho boundaries kiến trúc

Architectural rules thuộc về tests, không code review. Encode chúng thành NetArchTest assertions ("không class trong Domain reference EntityFrameworkCore") và chúng fail build thay vì fail standup.

public class ArchitectureTests
{
    [Fact]
    public void Domain_ShouldNot_DependOn_Infrastructure()
    {
        var result = Types.InAssembly(typeof(Order).Assembly)
            .ShouldNot()
            .HaveDependencyOn("EntityFrameworkCore")
            .GetResult();

        result.IsSuccessful.Should().BeTrue();
    }

    [Fact]
    public void Controllers_ShouldNot_Access_DbContext_Directly()
    {
        var result = Types.InAssembly(typeof(Program).Assembly)
            .That().ResideInNamespace("MyApp.Controllers")
            .ShouldNot()
            .HaveDependencyOn("Microsoft.EntityFrameworkCore")
            .GetResult();

        result.IsSuccessful.Should().BeTrue();
    }
}

Lý do: Mỗi khi bạn add một layer mới hoặc project mới, chạy NetArchTest tự động check rằng architectural rules không bị vô tình phá vỡ. Cursor sẽ thoải mái reference infrastructure từ domain khi "fix" một bug , NetArchTest catch nó ngay tại build time.

Rule 18 , WebApplicationFactory cho Integration Tests

In-memory EF Core providers nói dối. Dùng WebApplicationFactory với Testcontainers (SQL Server, Postgres) cho real integration coverage. Cursor defaults sang UseInMemoryDatabase , pass local nhưng ship bug lên production.

// Cursor thường generate ,  sai
services.AddDbContext<AppDbContext>(opt =>
    opt.UseInMemoryDatabase("TestDb"));

// Senior rule ,  đúng
public class ApiFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestContainer<MsSqlContainer>(container =>
        {
            container.UseImage("mcr.microsoft.com/mssql/server:2022-latest");
        });

        builder.ConfigureServices(services =>
        {
            services.AddDbContext<AppDbContext>(opt =>
                opt.UseSqlServer(_container.GetConnectionString()));
        });
    }
}

Lý do: UseInMemoryDatabase không enforce constraints, transactions, hoặc SQL syntax. Real database capture bugs mà in-memory miss , nullability, cascades, indexes, JSON columns. Testcontainers chạy real SQL Server/Postgres trong Docker, không cần setup gì trên CI runner.

TL;DR cho team

# Cursor hay làm Senior rule
24 Swap Task ↔ ValueTask random ValueTask chỉ cho sync hot paths
23 Bool flag parameters Refactor thành types hoặc tách method
22 Task.Run trong controller BackgroundService + Channel
21 ConcurrentQueue + SemaphoreSlim Channel
19 Domain reference infrastructure NetArchTest trong CI
18 UseInMemoryDatabase cho tests WebApplicationFactory + Testcontainers

Mỗi rule này đều có thể encode thành Cursor rule (arch-core-lite.mdc chẳng hạn) , install trong 60 giây, Cursor sẽ tự catch trước khi đề xuất pattern sai.

Source


Son Do , BKGlobal Tech Team

#BKGlobal #cursor #dotnet #ai-coding-tools #senior-dev

Bài liên quan:

- AI coding tools: con số 10x là marketing, thực tế chỉ 25-40% , khi dùng Cursor/Copilot, 6 rules này giữ năng suất thật ở mức 25-40% chứ không xuống dưới 0

- OpenAI giới hạn rollout GPT-5.6 theo yêu cầu chính phủ Mỹ , Cursor dùng GPT-5.6, đây là tin tức ảnh hưởng trực tiếp đến workflow AI coding của bạn


Bài viết liên quan

Xem thêm
Tin tức Công nghệ

OpenAI giới hạn rollout GPT-5.6 theo yêu cầu chính phủ Mỹ - điều này ảnh hưởng đến dự án AI của bạn thế nào

Cuối tháng 6 năm 2026, OpenAI công bố GPT-5.6 với ba phiên bản: Sol (flagship), Terra (cân bằng), Luna (nhanh, rẻ). Nhưng ngay khi ra mắt, việc rollout bị giới hạn cho "một nhóm nhỏ đối tác tin cậy" theo yêu cầu của chính phủ Mỹ. Đây không chỉ là tin chính trị - nó ảnh hưởng trực tiếp đến developer và doanh nghiệp đang tích hợp OpenAI API. Bài này phân tích thay đổi kỹ thuật, ý nghĩa cho team Việt Nam, và chiến lược ứng phó của BKGlobal.