首页
学习
活动
专区
圈层
工具
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

解锁生产力:Cursor 如何将您的编码效率提升 2-3 倍

如果你懂得如何正确使用它

图片由 OpenAI 生成

首先要说明的是:我不是人工智能的鼓吹者。我担心它将对我们的工作和文化产生影响,并且我对人们为它找到的许多应用持怀疑态度。我也担心它会让我们变得懒惰。

无论这些担忧是否站得住脚,我必须承认人工智能的能力现在非常令人印象深刻——考虑到我们创业公司目前面临的财务压力,如果我不采用它,那将是愚蠢的。

几个月前,我们迁移到 Cursor 作为我们的 IDE,从那以后我就一直对它很着迷。它明显提高了我的产出 2-3 倍,甚至可能更多。

今天早上,我在上午 10 点之前解决了两个原本可能需要我一天大部分时间才能完成的工单。这让我有时间去商店、泡咖啡……然后写下这个故事!更重要的是,我的大脑仍然很清醒,所以我应该能够在午餐后完成更多的工作。

然而,最近在一次辅导课程中,我与一些学生交谈,询问他们对人工智能的总体看法,特别是 Cursor,他们说他们的团队仍在评估它。事实上,他们似乎普遍不以为然——这向我表明,并非所有人都了解如何最好地利用它。

对我来说,作为一名软件工程师,在使用人工智能时有三件重要的事情要记住:

1. 它不是为了帮你解决工程问题;它是为了帮助你加速实现你已经看到的解决方案。

2. 它需要正确的上下文才能给出正确的答案

3. 就像对人类一样,当你分解事物时,结果会更好

这意味着什么?

这意味着如果你只是懒惰地给它高层次的指令,而没有考虑最终结果,那么这些结果很可能质量很低。

但是,如果你了解你想要构建什么,正确地分解它并表达正确的上下文、规则和指导方针,那么你可能会对人工智能能够完成的工作感到惊喜。

一个实际的例子——构建一个事务 API

为了说明我的意思,让我们通过使用 Cursor 构建一个 API 端点的过程。

我将使用 Cursor 的 Composer 模式,它允许它更改应用程序中的多个文件。首先,我将给它一个故意幼稚的提示,然后我们将改进它以了解我们如何做得更好。

第一次尝试——幼稚的提示

这是最初的提示:

我想添加针对财务模型记录单个事务的能力。

请创建一个 Transaction 实体并添加到 dbcontext。请添加 API 端点,并提供用于创建、读取、更新和删除事务的适当查询和命令。

给你一些背景信息——我正在构建一个简单的预算规划应用程序(基本上我想替换一个我维护了大约 10 年的电子表格),并且我正在使用 C#。

以下是提示输出的示例。我没有包含全部内容,只是足够让你了解:

using Microsoft.AspNetCore.Mvc;

using Microsoft.EntityFrameworkCore;

using Trubble.Db;

namespaceTrubble.Api.Domains.System;

[ApiController]

[Route("transactions")]

publicclassTransactionController : ControllerBase

{

  privatereadonlyTrubbleDbContext _dbContext;

publicTransactionController(TrubbleDbContext dbContext)

  {

      _dbContext = dbContext;

  }

[HttpGet]

  publicasyncTask<ActionResult<List<TransactionResponse>>> GetTransactions()

  {

      var transactions = await _dbContext.Transactions

          .OrderByDescending(t => t.TransactionDate)

          .Select(t =>newTransactionResponse

          {

              Id = t.Id,

              Amount = t.Amount,

              Description = t.Description,

              TransactionDate = t.TransactionDate,

              Type = t.Type,

              Reference = t.Reference,

              CreatedAt = t.CreatedAt,

              UpdatedAt = t.UpdatedAt

          })

          .ToListAsync();

returnOk(transactions);

  }

...

[HttpPut("{id}")]

  publicasyncTask<ActionResult<TransactionResponse>> UpdateTransaction(int id, UpdateTransactionRequest request)

  {

      var transaction = await _dbContext.Transactions.FindAsync(id);

if (transaction == null)

      {

          returnNotFound();

      }

transaction.Amount = request.Amount;

      transaction.Description = request.Description;

      transaction.TransactionDate = request.TransactionDate;

      transaction.Type = request.Type;

      transaction.Reference = request.Reference;

      transaction.UpdatedAt = DateTime.UtcNow;

await _dbContext.SaveChangesAsync();

var response = newTransactionResponse

      {

          Id = transaction.Id,

          Amount = transaction.Amount,

          Description = transaction.Description,

          TransactionDate = transaction.TransactionDate,

          Type = transaction.Type,

          Reference = transaction.Reference,

          CreatedAt = transaction.CreatedAt,

          UpdatedAt = transaction.UpdatedAt

      };

returnOk(response);

  }

}

那么它做得怎么样?

嗯,它有效,大部分情况下。我没有分享所有代码——它也对数据层进行了更改——并且与数据库的集成是有效的。如果我向任何端点发送请求,它将发送回预期的响应。它遗漏的一件事是事务和财务模型之间的关系。

但是代码质量如何呢?

诚然,这是一个非常简单的要求,因此将所有内容放在一个文件中可能感觉很合理,但这不符合应用程序的编码标准。具体来说,这里没有关注点分离——控制器试图做所有事情。

现在,我可以将其重构为命令和查询(我在这个项目中使用了带有垂直切片架构的 CQS 模式),但这不是我想要的。

我希望人工智能做得更好。

第二次尝试 — 包含编码标准

AI 需要一些关于编码标准的上下文信息,这样它才能理解我们在哪里划清界限,以及我们如何在特定项目中构建代码。

幸运的是,在 cursor 中,我们可以在“设置”中为我们的 IDE 添加一些通用规则。 每次我们发出请求时,这些规则都会被加载到上下文中。

所以,现在让我们添加一些规则:

采用关注点分离原则 — 类/函数应该只承担单一职责

存在针对不同关注层的单独项目 — API 层、域层和数据层

对于每个类 Foo,都应该在同一个文件的顶部有一个接口 IFoo — 除非它实现了另一个有多个实现的接口。

类通过 Autofac 自动连接使用 AsImplementedInterfaces 进行依赖注入。 无需添加额外的依赖注入配置

业务逻辑属于域层中的查询和命令。

我们可以改进这些规则 — 并且随着时间的推移,我们可以添加更多规则 — 但我发现,与其试图一次性编写所有内容,不如迭代,并且每次 AI 做错事时,添加一个规则或更改一个现有规则。

现在,让我们使用与之前相同的提示。 输出结果是什么样的?

using Microsoft.AspNetCore.Mvc;

using System;

using System.Collections.Generic;

using System.Linq;

using System.Threading.Tasks;

using Trubble.Api.Models;

using Trubble.Domain.Commands;

using Trubble.Domain.Queries;

using Trubble.Api.Mappers;

namespaceTrubble.Api.Controllers

{

  [ApiController]

  [Route("api/[controller]")]

  publicclassTransactionController : ControllerBase

  {

      privatereadonlyIGetTransactionQuery _getTransactionQuery;

      privatereadonlyIGetTransactionsQuery _getTransactionsQuery;

      privatereadonlyICreateTransactionCommand _createTransactionCommand;

      privatereadonlyIUpdateTransactionCommand _updateTransactionCommand;

      privatereadonlyIDeleteTransactionCommand _deleteTransactionCommand;

      privatereadonlyITransactionMapper _transactionMapper;

      publicTransactionController(

          IGetTransactionQuery getTransactionQuery,

          IGetTransactionsQuery getTransactionsQuery,

          ICreateTransactionCommand createTransactionCommand,

          IUpdateTransactionCommand updateTransactionCommand,

          IDeleteTransactionCommand deleteTransactionCommand,

          ITransactionMapper transactionMapper)

      {

          _getTransactionQuery = getTransactionQuery;

          _getTransactionsQuery = getTransactionsQuery;

          _createTransactionCommand = createTransactionCommand;

          _updateTransactionCommand = updateTransactionCommand;

          _deleteTransactionCommand = deleteTransactionCommand;

          _transactionMapper = transactionMapper;

      }

      [HttpGet]

      publicasyncTask<ActionResult<List<TransactionDto>>> GetTransactions()

      {

          var transactions = await _getTransactionsQuery.ExecuteAsync();

          return transactions.Select(_transactionMapper.ToDto).ToList();

      }

      [HttpGet("{id}")]

      publicasyncTask<ActionResult<TransactionDto>> GetTransaction(int id)

      {

          var transaction = await _getTransactionQuery.ExecuteAsync(id);

          if (transaction == null)

              returnNotFound();

          return _transactionMapper.ToDto(transaction);

      }

      [HttpPost]

      publicasyncTask<ActionResult<TransactionDto>> CreateTransaction(CreateTransactionDto createDto)

      {

          var transaction = _transactionMapper.ToDomain(createDto);

          var result = await _createTransactionCommand.ExecuteAsync(transaction);

          return _transactionMapper.ToDto(result);

      }

      [HttpPut("{id}")]

      publicasyncTask<ActionResult<TransactionDto>> UpdateTransaction(int id, UpdateTransactionDto updateDto)

      {

          var transaction = await _getTransactionQuery.ExecuteAsync(id);

          if (transaction == null)

              returnNotFound();

          _transactionMapper.UpdateDomain(transaction, updateDto);

          var result = await _updateTransactionCommand.ExecuteAsync(transaction);

          return _transactionMapper.ToDto(result);

      }

      [HttpDelete("{id}")]

      publicasyncTask<ActionResult> DeleteTransaction(int id)

      {

          await _deleteTransactionCommand.ExecuteAsync(id);

          returnNoContent();

      }

  }

}

这好多了! 它为不同的端点创建了单独的查询和命令,并且此类中的大多数(但并非全部)逻辑是合适的。 这些命令的布局或多或少是正确的,例如:

using Trubble.Db;

namespaceTrubble.Core.Domains.Transactions.Commands;

publicinterfaceIDeleteTransactionCommand

{

  TaskExecuteAsync(int id);

}

publicclassDeleteTransactionCommand : IDeleteTransactionCommand

{

  privatereadonlyTrubbleDbContext _context;

  publicDeleteTransactionCommand(TrubbleDbContext context)

  {

      _context = context;

  }

  publicasyncTaskExecuteAsync(int id)

  {

      var entity = await _context.Transactions.FindAsync(id)

          ?? thrownewKeyNotFoundException($"Transaction {id} not found");

      _context.Transactions.Remove(entity);

      await _context.SaveChangesAsync();

  }

}

不过,仍然存在一些问题,包括:

• UpdateTransaction 方法做得太多了

• API 级别的 DTO 已在域层中定义

• 它仍然没有添加交易和财务模型之间的正确关系

• 它为 TransactionModel 添加了一个不必要的接口(需要为命令和查询定义接口以进行依赖注入和测试,但 DTO 和域模型不需要它们)。

我们仍然可以做得更好。 我还没有给它足够的上下文 — 或者我给它的一些上下文是不完整的。

第三次尝试 — 使用本地设置和文件上下文

除了通用的编码标准之外,Cursor 还允许你定义本地项目设置。它是一个 JSON 文件,但你不需要理解其模式,因为你可以要求 AI 为你生成该文件。

在我们的第三次尝试之前,我将要求 Cursor 生成一个设置文件,该文件映射此项目中的一些代码并定义一些额外的规则。这是输出,它将其保存到.cursor/settings.json:

{

  "projectLayout":{

      "rootDir":"api/Trubble.Api",

      "projects":{

          "Trubble.Api":{

              "path":"Trubble.Api",

              "description":"API layer containing controllers, DTOs, and mappers organized by domain"

          },

          "Trubble.Domain":{

              "path":"Trubble.Domain",

              "description":"Domain layer containing business logic, commands, queries, and domain models organized by domain"

          },

          "Trubble.Db":{

              "path":"Trubble.Db",

              "description":"Database layer containing DbContext, configurations, and migrations"

          },

          "Trubble.Common":{

              "path":"Trubble.Common",

              "description":"Common utilities, domain-agnostic classes, enums, and shared components"

          }

      },

      "conventions":{

          "Trubble.Api":{

              "DomainStructure":"Domains/{DomainName}/*",

              "Controllers":"Domains/{DomainName}/*Controller.cs",

              "Dtos":"Domains/{DomainName}/*Dto.cs",

              "Mappers":"Domains/{DomainName}/*Mapper.cs"

          },

          "Trubble.Domain":{

              "DomainStructure":"Domains/{DomainName}/*",

              "Commands":"Domains/{DomainName}/Commands/{Verb}{Action}Command.cs",

              "Queries":"Domains/{DomainName}/Queries/{Verb}{Action}Query.cs",

              "Models":"Domains/{DomainName}/Models/*.cs"

          },

          "Trubble.Db":{

              "Context":"TrubbleDbContext.cs",

              "Entities":"Entities/*.cs",

              "Configurations":"Configurations/*.cs",

              "Migrations":"Migrations/*.cs"

          },

          "Trubble.Common":{

              "Utils":"Utils/*.cs",

              "Enums":"Enums/*.cs",

              "Events":"Events/*.cs"

          }

      },

      "namingConventions":{

          "queries":"{Verb}{Action}Query",

          "commands":"{Verb}{Action}Command",

          "controllers":"{Domain}Controller",

          "dtos":"{Action}{Domain}Dto",

          "mappers":"{Domain}Mapper"

      },

      "architecture":{

          "type":"VerticalSlice",

          "domainFolder":"Domains",

          "domainStructure":{

              "api":"{DomainName}/{Controller,Dtos,Mapper}",

              "domain":"{DomainName}/{Commands,Queries,Models}"

          }

      }

  }

}

从表面上看,这看起来只是在映射文件的命名约定,但它也告诉 Cursor 很多关于我们系统的高级架构的信息。 事实上,存在诸如 Queries、Commands 和 Mappers 之类组件的位置,这告诉它这些是它应该作为解决方案一部分生成的内容。

更重要的是,现在我们已经定义了这些位置,它可以在文件中添加与这些位置直接相关的额外规则。 这是我日常工作的一个例子,这次是在前端:

"components": {

    "atomic":{

      "atoms":{

        "path":"components/atoms/{category}/{componentName}.jsx",

        "rules":[

          "Must be stateless",

          "Should be purely presentational",

          "Should be highly reusable"

        ]

      },

      "molecules":{

        "path":"components/molecules/{category?}/{componentName}.jsx",

        "rules":[

          "May contain presentational state",

          "Should not contain business logic",

          "Should not make API calls",

          "Should compose atoms and other molecules"

        ]

      },

      "organisms":{

        "path":"components/organisms/{domain}/{componentName}.jsx",

        "structure":{

          "mainComponent":"{path}",

          "subComponents":"components/organisms/{domain}/",

          "hooks":"components/organisms/{domain}/hooks/",

          "graphql":{

            "queries":"components/organisms/{domain}/graphql/*.graphql",

            "api":"components/organisms/{domain}/graphql/*.api.js"

          }

        },

        "rules":[

          "May contain complex state",

          "Business logic should be extracted to custom hooks",

          "API calls should be isolated in graphql folder",

          "Should use custom hooks for data fetching, not API hooks directly"

        ]

      }

    }

  }

},

在这里,我们不仅映射了文件应该存在的位置,还定义了关于不同文件应该关注什么的指导。

那么,我们准备好再次尝试了吗?

还没。

在我们进行第三次尝试之前,我们需要做的最后一件事是向上下文中添加几个文件。 虽然 AI 现在可以根据这些规则导航大部分代码库,但这仅适用于生成新文件。 如果我们希望它与现有文件交互,那么我们也必须向它提供该上下文。

Cursor 让你通过选择一个文件并将其添加到当前的 Compose 聊天中来做到这一点。 我将添加数据库上下文和 FinancialModel 实体,因为我希望它添加与 Transaction 的一对多关系。

好的,完成了。 我们开始吧!

using Microsoft.AspNetCore.Mvc;

using Trubble.Core.Domains.Transactions.Commands;

using Trubble.Core.Domains.Transactions.Queries;

namespaceTrubble.Api.Domains.Transactions;

[ApiController]

[Route("api/[controller]")]

publicclassTransactionsController : ControllerBase

{

  privatereadonlyITransactionsMapper _mapper;

  privatereadonlyICreateTransactionCommand _createCommand;

  privatereadonlyIUpdateTransactionCommand _updateCommand;

  privatereadonlyIDeleteTransactionCommand _deleteCommand;

  privatereadonlyIGetTransactionQuery _getQuery;

  privatereadonlyIGetModelTransactionsQuery _getModelTransactionsQuery;

publicTransactionsController(

      ITransactionsMapper mapper,

      ICreateTransactionCommand createCommand,

      IUpdateTransactionCommand updateCommand,

      IDeleteTransactionCommand deleteCommand,

      IGetTransactionQuery getQuery,

      IGetModelTransactionsQuery getModelTransactionsQuery)

  {

      _mapper = mapper;

      _createCommand = createCommand;

      _updateCommand = updateCommand;

      _deleteCommand = deleteCommand;

      _getQuery = getQuery;

      _getModelTransactionsQuery = getModelTransactionsQuery;

  }

[HttpGet("{id}")]

  publicasyncTask<Actio

## TransactionController.cs

```csharp

[ApiController]

[Route("[controller]")]

public class TransactionController : ControllerBase

{

  private readonly IMapper _mapper;

  private readonly IQuery<Transaction, int> _getQuery;

  private readonly IQuery<List<Transaction>, int> _getModelTransactionsQuery;

  private readonly ICommand<Transaction> _createCommand;

  private readonly ICommand<Transaction> _updateCommand;

  private readonly ICommand<int> _deleteCommand;

  public TransactionController(IMapper mapper,

      IQuery<Transaction, int> getQuery,

      IQuery<List<Transaction>, int> getModelTransactionsQuery,

      ICommand<Transaction> createCommand,

      ICommand<Transaction> updateCommand,

      ICommand<int> deleteCommand)

  {

      _mapper = mapper;

      _getQuery = getQuery;

      _getModelTransactionsQuery = getModelTransactionsQuery;

      _createCommand = createCommand;

      _updateCommand = updateCommand;

      _deleteCommand = deleteCommand;

  }

  [HttpGet("{id}")]

  public async Task<ActionResult<TransactionDto>> GetTransaction(int id)

  {

      var model = await _getQuery.ExecuteAsync(id);

      return Ok(_mapper.ToDto(model));

  }

  [HttpGet("model/{modelId}")]

  public async Task<ActionResult<List<TransactionDto>>> GetModelTransactions(int modelId)

  {

      var models = await _getModelTransactionsQuery.ExecuteAsync(modelId);

      return Ok(models.Select(_mapper.ToDto));

  }

  [HttpPost]

  public async Task<ActionResult<TransactionDto>> CreateTransaction(CreateTransactionDto dto)

  {

      var model = await _createCommand.ExecuteAsync(_mapper.ToModel(dto));

      return Ok(_mapper.ToDto(model));

  }

  [HttpPut("{id}")]

  public async Task<ActionResult<TransactionDto>> UpdateTransaction(int id, UpdateTransactionDto dto)

  {

      var model = await _updateCommand.ExecuteAsync(_mapper.ToModel(id, dto));

      return Ok(_mapper.ToDto(model));

  }

  [HttpDelete("{id}")]

  public async Task<ActionResult> DeleteTransaction(int id)

  {

      await _deleteCommand.ExecuteAsync(id);

      return NoContent();

  }

}

一个单一的命令已经搭建了数据库实体、命令和查询、控制器以及各种映射代码。它在很大程度上遵循了我的编码标准,并按照我希望的方式完成了工作——这一次它甚至添加了正确的关系!

同样重要的是,当我运行代码时,它就能正常工作。

上下文为王

诚然,这个例子相当简单,而且是人为设计的,并没有包含很多复杂的逻辑。对于复杂的逻辑,我仍然认为工程师应该自己编写代码。

然而,Cursor(以及底层的 LLM 模型)可以做的,是消除很多样板代码。上面编写的代码对我来说并不难,但仍然需要花费一些时间——而且到最后我会感到厌烦和疲惫。

正确使用 Cursor——给它正确的上下文,并严格控制它所遵循的编码标准——让我可以专注于真正重要的事情。

一个令人高兴的副作用是,我进行的上下文切换更少了,所以我可以真正深入地研究问题,这在过去我有时会感到很吃力(初创公司可能就是这样)。

因此,虽然你不应该用它来编写高度优化的代码或复杂的、专有的业务逻辑,但我认为它对于软件开发的更基础的方面绝对是极好的。

最终的额外提示——代码模板

最后一件事:

我在本文中尚未探讨,但值得注意的是代码模板。

代码模板可以超越描述规则和约定,实际上是展示 AI 代码应该是什么样子。如果你将它们与本地设置文件结合使用(这样它就可以轻松地关联哪个模板与你的代码库的哪一部分相关),那么这可能非常强大。

同样,你不必自己编写它们。一旦你确定了代码中一个你希望 Cursor 遵循的常见模式,你可以要求它生成一个模板,存储在 .cursor/templates 中。

例如,这是一个用于 InputBuilder 的模板,我们将其用作测试框架的一部分:

using System;

using FluentAssertions;

using Hostology.ApiTests.DataSeeding;

using Hostology.GraphQL.API.{{domain}};

namespaceHostology.ApiTests.Builders;

publicclass {{model}}InputBuilder

{

  privatereadonly {{model}}InputType _instance;

public {{model}}InputBuilder()

  {

      _instance = new {{model}}InputType

      {

          {{#each defaultProperties}}

          {{name}} = {{{defaultValue}}},

          {{/each}}

      };

  }

protected {{model}}InputBuilder({{model}}InputType instance) =>

      _instance = new()

      {

          {{#each properties}}

          {{name}} = instance.{{name}},

          {{/each}}

      };

{{#each properties}}

  public {{../model}}InputBuilderWith{{name}}({{type}} {{camelCase name}})

  {

      _instance.{{name}} = {{camelCase name}};

      returnthis;

  }

{{/each}}

public {{model}}InputTypeBuild() => _instance;

publicvoidShouldMatch({{model}}Type model)

  {

      model.Should().NotBeNull();

      {{#each assertProperties}}

      {{#if customAssertion}}

      {{{customAssertion}}}

      {{else}}

      model.{{name}}.Should().Be(_instance.{{name}});

      {{/if}}

      {{/each}}

  }

public {{model}}InputBuilderDuplicate() => new(_instance);

}

为了构建这个,我只提供了几个现有测试的例子,Cursor 创建了一个 Handlebars 模板,它可以在生成未来的模型时使用。

从那以后,我已经多次要求它生成测试——并且它一直正确地获取了构建器,这与以前不同。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/O13Ck-W2x5D1zH473KPQDr4Q0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券