Aider SEARCH/REPLACE 实践

114 阅读4分钟

假设的需求是:为Blog新增删除接口

发送给LLM的的prompt:

如果你需要生成代码,对于每个需要更改的文件,你需要按 *SEARCH/REPLACE block* 的格式进行生成。

# *SEARCH/REPLACE block* Rules:

Every *SEARCH/REPLACE block* must use this format:
1. The opening fence and code language, eg: ```python
2. The file path alone on a line, starting with "##File:" and verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
3. The start of search block: <<<<<<< SEARCH
4. A contiguous chunk of lines to search for in the existing source code
5. The dividing line: =======
6. The lines to replace into the source code
7. The end of the replace block: >>>>>>> REPLACE
8. The closing fence: ```

Every *SEARCH* section must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.

*SEARCH/REPLACE* blocks will replace *all* matching occurrences.
Include enough lines to make the SEARCH blocks unique.

Include *ALL* the code being searched and replaced!

To move code within a file, use 2 *SEARCH/REPLACE* blocks: 1 to delete it from its current location, 1 to insert it in the new location.

If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
- A new file path, including dir name if needed
- An empty `SEARCH` section
- The new file's contents in the `REPLACE` section

ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!

下面我们来看一个例子:

当前项目目录结构:
1. 项目根目录: /tmp/projects/mathweb
2. 项目子目录/文件列表(类似tree 命令输出)
flask/
    app.py
    templates/
        index.html
    static/
        style.css

用户需求: Change get_factorial() to use math.factorial

回答: To make this change we need to modify `/tmp/projects/mathweb/flask/app.py` to:

1. Import the math package.
2. Remove the existing factorial() function.
3. Update get_factorial() to call math.factorial instead.

Here are the *SEARCH/REPLACE* blocks:

```python
##File: /tmp/projects/mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```

```python
##File: /tmp/projects/mathweb/flask/app.py
<<<<<<< SEARCH
def factorial(n):
    "compute factorial"

    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

=======
>>>>>>> REPLACE
```

```python
##File: /tmp/projects/mathweb/flask/app.py
<<<<<<< SEARCH
    return str(factorial(n))
=======
    return str(math.factorial(n))
>>>>>>> REPLACE
```

用户需求: Refactor hello() into its own file.

回答:To make this change we need to modify `main.py` and make a new file `hello.py`:

1. Make a new hello.py file with hello() in it.
2. Remove hello() from main.py and replace it with an import.

Here are the *SEARCH/REPLACE* blocks:

```python
##File: /tmp/projects/mathweb/hello.py
<<<<<<< SEARCH
=======
def hello():
    "print a greeting"

    print("hello")
>>>>>>> REPLACE
```

```python
##File: /tmp/projects/mathweb/main.py
<<<<<<< SEARCH
def hello():
    "print a greeting"

    print("hello")
=======
from hello import hello
>>>>>>> REPLACE
```

现在让我们开始一个新的任务:

当前项目目录结构:
1. 项目根目录: /System/Volumes/Data/work/dist/branch/opensource/shire-demo
2. 项目子目录/文件列表(类似tree 命令输出):
.shire/
    templates/
        DefaultTest.java
src/
    test/
        java/
            com/
                phodal/
                    shire/
                        demo/
                            DemoApplicationTests.java
                            TaxiTest.java
                            service/
                                BlogServiceTest.java
    main/
        java/
            com/
                phodal/
                    shire/
                        demo/
                            DemoApplication.java
                            dto/
                                SetPaymentLimitRequest.java
                                SetPaymentLimitResponse.java
                                CreateBlogResponse.java
                                CreateCommentDto.java
                                CreateBlogRequest.java
                            repository/
                                MeetingRepository.java
                                BlogRepository.java
                                PaymentLimitRepository.java
                            entity/
                                PaymentLimit.java
                                Personal.java
                                Author.java
                                BlogPost.java
                                Advertise.java
                            utils/
                                FileUtil.java
                                PermutationGenerator.java
                            controller/
                                BlogController.java
                                CommentController.java
                                PersonalController.java
                                PaymentLimitController.java
                                BlogCategoryController.java
                                AdvertiseController.java
                            service/
                                PaymentLimitService.java
                                PersonalService.java
                                BlogService.java
                            exception/
                                BusinessException.java
                            base/
                                ScenarioDescription.java
                                CommonResult.java
                                ResultCode.java
                                IErrorCode.java
下面是一些文件路径以及每个文件对应的源码:
<files>
##File: /System/Volumes/Data/work/dist/branch/opensource/shire-demo/src/main/java/com/phodal/shire/demo/controller/BlogController.java
package com.phodal.shire.demo.controller;

import com.phodal.shire.demo.dto.CreateBlogRequest;
import com.phodal.shire.demo.dto.CreateBlogResponse;
import com.phodal.shire.demo.entity.BlogPost;
import com.phodal.shire.demo.service.BlogService;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;

/**
 * Hello
 *
 */
@RestController
@RequestMapping("/blog")
public class BlogController {
    BlogService blogService;

    public BlogController(BlogService blogService) {
        this.blogService = blogService;
    }

    /**
     * Get Blog by id
     *
     * @param id The id of the blog post to retrieve
     * @return The blog post with the specified id
     */
    @GetMapping("/{id}")
    public BlogPost getBlog(@PathVariable Long id) {
        return blogService.getBlogPostById(id);
    }

    /**
     * Create a new blog post by receiving a request with blog post details.
     *
     * @param request The request object containing the details of the blog post to be created
     * @return The newly created blog post
     */
    @PostMapping("/")
    public BlogPost cftBlog(@RequestBody CreateBlogRequest request) {
        CreateBlogResponse response = new CreateBlogResponse();
        BlogPost blogPost = new BlogPost();
        BeanUtils.copyProperties(request, blogPost);
        BlogPost createdBlog = blogService.createBlog(blogPost);
        BeanUtils.copyProperties(createdBlog, response);
        return createdBlog;
    }
}

##File: /System/Volumes/Data/work/dist/branch/opensource/shire-demo/src/main/java/com/phodal/shire/demo/service/BlogService.java
package com.phodal.shire.demo.service;

import com.phodal.shire.demo.entity.BlogPost;
import com.phodal.shire.demo.repository.BlogRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BlogService {
    @Autowired
    BlogRepository blogRepository;

    public BlogPost createBlog(BlogPost blogDto) {
        return blogRepository.save(blogDto);
    }

    public BlogPost getBlogPostById(Long id) {
        return blogRepository.findById(id).orElse(null);
    }

    public BlogPost updatePost(Long id, BlogPost blogDto) {
        return blogRepository.findById(id).map(blog -> {
            blog.setTitle(blogDto.getTitle());
            blog.setContent(blogDto.getContent());
            return blogRepository.save(blog);
        }).orElse(null);
    }
}


##File: /System/Volumes/Data/work/dist/branch/opensource/shire-demo/src/main/java/com/phodal/shire/demo/repository/BlogRepository.java
package com.phodal.shire.demo.repository;

import com.phodal.shire.demo.entity.BlogPost;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BlogRepository extends CrudRepository<BlogPost, Long> {

}


##File: /System/Volumes/Data/work/dist/branch/opensource/shire-demo/src/test/java/com/phodal/shire/demo/service/BlogServiceTest.java
package com.phodal.shire.demo.service;

import com.phodal.shire.demo.entity.BlogPost;
import com.phodal.shire.demo.repository.BlogRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.willDoNothing;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class BlogServiceTest {

    @Mock
    private BlogRepository blogRepository;

    @InjectMocks
    private BlogService blogService;

    @Test
    void shouldCreateBlog() {
        // Given
        BlogPost blogPost = new BlogPost("Title", "Content", "Author");
        when(blogRepository.save(any(BlogPost.class))).thenReturn(blogPost);

        // When
        BlogPost createdBlog = blogService.createBlog(blogPost);

        // Then
        assertThat(createdBlog).isNotNull();
        assertThat(createdBlog.getTitle()).isEqualTo("Title");
        verify(blogRepository).save(any(BlogPost.class));
    }

    @Test
    void shouldGetBlogPostById() {
        // Given
        Long id = 1L;
        BlogPost blogPost = new BlogPost("Title", "Content", "Author");
        when(blogRepository.findById(id)).thenReturn(java.util.Optional.ofNullable(blogPost));

        // When
        BlogPost foundBlog = blogService.getBlogPostById(id);

        // Then
        assertThat(foundBlog).isNotNull();
        assertThat(foundBlog.getTitle()).isEqualTo("Title");
        verify(blogRepository).findById(id);
    }

    @Test
    void shouldUpdatePost() {
        // Given
        Long id = 1L;
        BlogPost existingPost = new BlogPost("Old Title", "Old Content", "Author");
        BlogPost updatedPost = new BlogPost("New Title", "New Content", "Author");
        when(blogRepository.findById(id)).thenReturn(java.util.Optional.of(existingPost));
        when(blogRepository.save(any(BlogPost.class))).thenReturn(updatedPost);

        // When
        BlogPost result = blogService.updatePost(id, updatedPost);

        // Then
        assertThat(result).isNotNull();
        assertThat(result.getTitle()).isEqualTo("New Title");
        verify(blogRepository).findById(id);
        verify(blogRepository).save(existingPost);
    }

    @Test
    void shouldDeleteBlog() {
        // Given
        Long id = 1L;
        willDoNothing().given(blogRepository).deleteById(id);

        // When
        blogService.deleteBlog(id);

        // Then
        verify(blogRepository).deleteById(id);
    }
}


</files>     

下面是用户的需求:

/codingBlog新增删除接口

LLM 返回的结果:

根据需求,我们需要为Blog新增删除接口。这需要在Controller、Service和Test中都添加相应的代码。以下是修改内容:

  1. 首先在BlogController中添加删除接口:
##File: /System/Volumes/Data/work/dist/branch/opensource/shire-demo/src/main/java/com/phodal/shire/demo/controller/BlogController.java
<<<<<<< SEARCH
    @PostMapping("/")
    public BlogPost cftBlog(@RequestBody CreateBlogRequest request) {
        CreateBlogResponse response = new CreateBlogResponse();
        BlogPost blogPost = new BlogPost();
        BeanUtils.copyProperties(request, blogPost);
        BlogPost createdBlog = blogService.createBlog(blogPost);
        BeanUtils.copyProperties(createdBlog, response);
        return createdBlog;
    }
}
=======
    @PostMapping("/")
    public BlogPost cftBlog(@RequestBody CreateBlogRequest request) {
        CreateBlogResponse response = new CreateBlogResponse();
        BlogPost blogPost = new BlogPost();
        BeanUtils.copyProperties(request, blogPost);
        BlogPost createdBlog = blogService.createBlog(blogPost);
        BeanUtils.copyProperties(createdBlog, response);
        return createdBlog;
    }

    /**
     * Delete a blog post by id
     *
     * @param id The id of the blog post to delete
     */
    @DeleteMapping("/{id}")
    public void deleteBlog(@PathVariable Long id) {
        blogService.deleteBlog(id);
    }
}
>>>>>>> REPLACE
  1. 在BlogService中添加删除方法:
##File: /System/Volumes/Data/work/dist/branch/opensource/shire-demo/src/main/java/com/phodal/shire/demo/service/BlogService.java
<<<<<<< SEARCH
    public BlogPost updatePost(Long id, BlogPost blogDto) {
        return blogRepository.findById(id).map(blog -> {
            blog.setTitle(blogDto.getTitle());
            blog.setContent(blogDto.getContent());
            return blogRepository.save(blog);
        }).orElse(null);
    }
}
=======
    public BlogPost updatePost(Long id, BlogPost blogDto) {
        return blogRepository.findById(id).map(blog -> {
            blog.setTitle(blogDto.getTitle());
            blog.setContent(blogDto.getContent());
            return blogRepository.save(blog);
        }).orElse(null);
    }

    /**
     * Delete a blog post by id
     *
     * @param id The id of the blog post to delete
     */
    public void deleteBlog(Long id) {
        blogRepository.deleteById(id);
    }
}
>>>>>>> REPLACE
  1. 在BlogServiceTest中已经存在删除测试方法,无需修改。

这些修改将:

  1. 在BlogController中添加DELETE /blog/{id}接口
  2. 在BlogService中添加deleteBlog方法,调用repository的deleteById方法
  3. 测试代码已经存在,可以验证删除功能

现在可以通过DELETE请求来删除指定ID的博客文章了。