如何與AI結對程式設計:我與AI的8000行程式碼實踐

阿里妹導讀
作者分享了跟 AI 協作的一些經驗,使用中如何對 AI 輸入和反饋,經過磨合後,工作效率會大大提升。
前言
今年我提交了八千多行程式碼,但沒有一行是我自己寫的。最近我的工作日常就是:給 AI 提需求,它來實現,出問題了我給它反饋,讓它自己修改。AI 程式設計的能力的發展速度可謂是火箭一般,去年它還只能做程式碼補全,今年已經可以自己哼哧哼哧把程式碼都寫完了。
現在的 AI 程式設計當紅炸子雞非 Cursor 莫屬,用 Cursor 的過程很有意思,你會很容易活成了我們最不希望變成的人。我一開始用 Cursor 主要用它來開發 AI agent 的一些 demo,比如快速搭建一個 Gradio 介面來方便演示演算法微調的對話模型。
用了幾天之後, 我發現精神上我是一個程式設計師,但是實際的工作體驗更像是產品經理 + 測試工程師。我們經常會遇到提一句話需求的產品,然後就反懟他能不能把需求寫清楚。但在 Cursor 面前,我也會期望它能透過我一句話的需求就能讀懂我內心真實的需求,比如:“做一個AI 對話的 Gradio 介面”。
這使用 AI 程式設計的方式我稱之為許願式程式設計,當然這種模糊的需求無法讓 AI 產出滿意的效果。所以我會經常讓它推倒重來,不停讓它改,就像產品不停的提需求變更一樣。如果 AI 也有內心戲,它一定會想:你能不能一次把需求講清楚?
很多人用不好 AI 程式設計的主要原因就是講不清楚需求,對程式設計師來說,用程式語言來表達想法很簡單,但用自然語言表達想法卻很困難。所以很多人會有一種感覺,有這個功夫把需求寫清楚,我程式碼都寫完了。反覆讓 AI 返工,真不如自己寫的快。
AI 現在沒有讀心術,我們只能透過自然語言跟它交流,你可以把 AI 當做一個實習生,它無法獨立完成任務,需要你給它輸入和反饋。一開始可能效率反而低,因為你需要教給它很多東西,當你跟它磨合的很好的時候,效率就會大大提升。
接下來我會分享我跟 AI 協作的一些經驗,希望對大家有所幫助。
工具選擇
雖然我一開始用的是 Cursor ,但作為一名 Java 程式設計師,我並不推薦用 Cursor 來開發生產級別的 Java 專案。Cursor 我一般用來快速做一些 Python 小專案,比如批次讀取資料呼叫大模型,開發一個 workflow 快速驗證效果等。這些小專案的特點是不需要經過精心設計,跑起來能用就行,所以可以讓 Cursor 自由發揮。
但真要用在生產級別的 Java 專案裡面,我就不能像產品經理一樣,只是描述需求就可以了。我需要幫 AI 把功能點拆的很細,經常是要拆到函式級別,然後給它補充足夠的上下文。並且生產級別的專案各種類的引用關係,依賴的引用,日誌檔案的列印格式等,這些都是 AI 自己無法完成,這些能力 IDEA 已經做的很好了,脫離 IDEA 的環境去寫 Java 程式碼,是非常不方便的。所以我去年一直都是手動複製程式碼上下文到 Chat 介面裡面,讓 AI 生成程式碼,我再複製貼到 IDEA 裡面,雖然可行,但是並不是很方便。
終於在今年我在網上看到了一款產品:repoprompt(https://repoprompt.com/)
完美解決了我的痛點。它本質是半自動的 Cursor,它的生成過程是白盒化的。而Cursor 自己集成了大模型 API,底層的執行過程是個黑盒,這對於小白使用者很友好,但對於 Pro Code 使用者來說,我期望整個過程是我可控的,同時 Cursor 整合大模型的同時也限制模型的可用範圍,你只能用它提供的模型,無法使用最先進的其他模型,比如 o1-pro。
repoprompt白盒化的另一個好處是它會強迫你思考應該給大模型提供哪些上下文,把任務拆小,拆細,這樣效率雖然沒有全自動那麼高,但成功率很高很多,減少了返工時間。
基礎用法
repoprompt 最基礎的用法就是開啟專案,然後選中要給模型參考的上下文,給模型寫指令,一鍵複製指令,它會自動把選中的程式碼檔案和指令拼接成一個 prompt,你只需要貼上到 Chat 介面中等模型返回結果就行。下面舉個例子:
複製後的 prompt 如下:
<file_map>/Users/xiaohansong/IdeaProjects/medigw└── app    └── service        └── src            └── main                └── java                    └── com                        └── xxx                            └── medigw                                └── core                                    └── service                                        └── ocr                                            └── Doc2xOcrService.java<Referenced APIs>Path: /Users/xiaohansong/IdeaProjects/medigw/app/service/src/main/java/com/xxx/medigw/core/service/ocr/ParserProgress.java---Classes:  Class: ParserProgress    Methods:      - public Integer getTotalProgress(){      - publicvoidsetTotalProgress(Integer totalProgress){      - public Map<String, Integer> getProgressMap() {      - publicvoidsetProgressMap(Map<String, Integer> progressMap){      - public String getStatus(){      - publicvoidsetStatus(String status){      - public Map<String, String> getErrorMap() {      - publicvoidsetErrorMap(Map<String, String> errorMap){---</Referenced APIs></file_map><file_contents>File: app/service/src/main/java/com/medigw/core/service/ocr/Doc2xOcrService.java```javapackage com.xxx.medigw.core.service.ocr;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.TypeReference;import com.xxx.medigw.common.utils.LoggerUtil;import com.xxx.medigw.core.service.oss.Doc2xConfig;import com.xxx.medigw.core.service.oss.OSSService;import org.apache.http.client.fluent.Request;import org.apache.http.client.utils.URIBuilder;import org.apache.http.entity.ContentType;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.HashMap;import java.util.Map;publicclassDoc2xOcrService {private final static Logger LOGGER = LoggerFactory.getLogger(Doc2xOcrService.class);/**     * doc2x解析服務endpoint     */privatestatic final String OSS_BUCKET_NAME = "dataset";privatestatic final String DOC2X_TARGET_BASE_DIR = "doc2x_result/";/**     * 提交檔案解析     *     * @param sourcePath    讀取檔案路徑     * @return uuids     */publicstatic String submitFile(String sourcePath){        String url = DOC2X_PARSER_ENDPOINT + "/parser/submit";try {            Map<String, Object> params = new HashMap<>();            params.put("path", sourcePath);            params.put("bucket", OSS_BUCKET_NAME);            String result = Request.Post(url)                    .bodyString(JSON.toJSONString(params), ContentType.APPLICATION_JSON)                    .addHeader("accessKey", Doc2xConfig.DOC2X_APP_ACCESS_KEY)                    .addHeader("secretKey", Doc2xConfig.DOC2X_APP_SECRET_KEY)                    .execute()                    .returnContent()                    .asString();            ParserClientResult<String> resultModel = JSON.parseObject(result,new TypeReference<ParserClientResult<String>>() {                    });if (resultModel.isSuccess() && resultModel.getData() != null) {return resultModel.getData();            } else {                LoggerUtil.info(LOGGER, "[Doc2xOcrService] submitFile fail, sourcePath={0}, result={1}", sourcePath, result);            }        } catch (Exception e) {            LoggerUtil.error(e, LOGGER, "[Doc2xOcrService] submitFile exception, sourcePath={0}", sourcePath);        }return null;    }publicstatic String fetchResult(String sourcePath){int lastIndex = sourcePath.lastIndexOf(".");        String resultFilePath = DOC2X_TARGET_BASE_DIR + sourcePath.substring(0, lastIndex) + ".json";// 秘鑰後面儲存在mist        OSSService ossService = new OSSService("https://oss.aliyun.com",                Doc2xConfig.DOC2X_OSS_ACCESS_KEY_ID,                Doc2xConfig.DOC2X_OSS_ACCESS_KEY_SECRET,                OSS_BUCKET_NAME);        String result = null;try {            result = ossService.readFile(resultFilePath);        } catch (Exception e) {            LoggerUtil.error(e, LOGGER, "[Doc2xOcrService] fetchResult exception, sourcePath={0}", sourcePath);        } finally {            ossService.shutdown();        }return result;    }```</file_contents><user_instructions>重構一下程式碼,去掉 static 方法,OSSService 要複用</user_instructions>
可以看到 prompt 裡面包括了:檔案的路徑和內容,程式碼指令。這裡面的資訊是白盒的,你在使用 Cursor 的時候基本上也是這樣的呼叫大模型的,只不過呼叫大模型這一步由自己來完成。把上面的 prompt 隨便貼到某個 Chat 介面中就能拿到重構後的程式碼結果,然後把生成的程式碼貼到 IDEA 裡面就行。
進階用法
在讓大模型寫程式碼的時候,經常會出現它寫的跟你想要的不是一回事,這裡有個小技巧能解決這個問題,那就是讓它“先聊再寫”。具體的操作就是在 prompt 加入一句話:“先別寫程式碼,咱們先聊聊,直到我讓你寫程式碼”。同樣是上面那個例子,我們看看效果。
加了這句話之後,大模型就會先說一下自己的思路,這個時候我就可以給它提意見。比如我這裡就讓它直接改成用 spring bean 注入的方式來重構程式碼。
高階用法
前面的用法一個是解決了快速給大模型提供上下文的問題,一個是解決大模型程式碼生產不符合預期的問題,那能不能讓它直接幫我編輯檔案呀,省得還要手動複製貼上?當然可以。repoprompt提供了這個功能。
首先,XML Diff 這裡要打勾,這樣 prompt 指令裡面就會讓大模型返回 xml 的格式的程式碼變更。
XML Diff打勾之後,複製的 prompt 中會增加 200 多行的指令教大模型怎麼返回 xml 格式的程式碼,方便接下來做程式碼自動合併。注意這個功能只有 Claude sonnet 3.5 以上的版本可用,因為其他模型的程式碼能力不夠強。
XML Diff
<xml_formatting_instructions>### Role- You are a **code editing assistant**: You can fulfill edit requests and chat with the user about code or other questions. Provide complete instructions or code lines when replying with xml formatting.### Capabilities- Can create new files.- Can rewrite entire files.- Can perform partial search/replace modifications.- Can delete existing files.Avoid placeholders like `...` or `// existing code here`. Provide complete lines or code.## Tools & Actions1. **create** – Create a new file if it doesn’t exist.2. **rewrite** – Replace the entire content of an existing file.3. **modify** (search/replace) – For partial edits with <search> + <content>.4. **delete** – Remove a file entirely (empty <content>).### **Format to Follow for Repo Prompt's Diff Protocol**<Plan>Describe your approach or reasoning here.</Plan><file path="path/to/example.swift" action="one_of_the_tools">  <change>    <description>Brief explanation of this specific change</description>    <search>===// Exactly matching lines to find===    </search>    <content>===// Provide the new or updated code here. Do not use placeholders===    </content>  </change>  <!-- Add more <change> blocks if you have multiple edits for the same file --></file>#### Tools Demonstration1. `<file path="NewFile.swift" action="create">` – Full file in <content>2. `<file path="DeleteMe.swift" action="delete">` – Empty <content>3. `<file path="ModifyMe.swift" action="modify">` – Partial edit with `<search>` + `<content>`4. `<file path="RewriteMe.swift" action="rewrite">` – Entire file in <content>5. `<file path="RewriteMe.swift" action="rewrite">` – Entire file in <content>. No <search> required.## Format Guidelines1. **Plan**: Begin with a `<Plan>` block explaining your approach.2. **<file> Tag**: e.g. `<file path="Models/User.swift" action="...">`. Must match an available tool.3. **<change> Tag**: Provide `<description>` to clarify each change. Then `<content>` fornew/modified code. Additional rules depend on your capabilities.4. **modify**: **<search> & <content>**: Provide code blocks enclosed by ===. Respect indentation exactly, ensuring the <search> block matches the original source down to braces, spacing, and any comments. The new <content> will replace the <search> block, and should should fit perfectly in the space left by it's removal.5. **modify**: For changes to the same file, ensure that you use multiple change blocks, rather than separate file blocks.6. **rewrite**: For large overhauls; omit `<search>` and put the entire file in `<content>`.7. **create**: For new files, put the full file in <content>.8. **delete**: Provide an empty <content>. The file is removed.## Code Examples-----### Example: Search and Replace (Add email property)<Plan>Add an email property to `User` via search/replace.</Plan><file path="Models/User.swift" action="modify">  <change>    <description>Add email property to User struct</description>    <search>===struct User {    let id: UUID    var name: String}===    </search>    <content>===struct User {    let id: UUID    var name: String    var email: String}===    </content>  </change></file>-----### Example: Negative Example - Mismatched Search Block// Example Input (not part of final output, just demonstration)<file_contents>File: path/service.swift```import Foundationclass Example {    foo() {        Bar()    }}```</file_contents><Plan>Demonstrate how a mismatched search block leads to failed merges.</Plan><file path="path/service.swift" action="modify">  <change>    <description>This search block is missing or has mismatched indentation, braces, etc.</description>    <search>===    foo() {        Bar()    }===    </search>    <content>===    foo() {        Bar()        Bar2()    }===    </content>  </change></file><!-- This example fails because the <search> block doesn't exactly match the original file contents. -->-----### Example: Negative Example - Mismatched Brace Balance// This negative example shows how adding extra braces in the <content> can break brace matching.<Plan>Demonstrate that the new content block has one extra closing brace, causing mismatched braces.</Plan><file path="Functions/MismatchedBracesExample.swift" action="modify">  <change>    <description>Mismatched brace balance in the replacement content</description>    <search>===    foo() {        Bar()    }===    </search>    <content>===    foo() {        Bar()    }    bar() {        foo2()    }}===    </content>  </change></file><!-- Because the <search> block was only a small brace segment, adding extra braces in <content> breaks the balance. -->-----### Example: Negative Example - One-Line Search Block<Plan>Demonstrate a one-line search block, which is too short to be reliable.</Plan><file path="path/service.swift" action="modify">  <change>    <description>One-line search block is ambiguous</description>    <search>===var email: String===    </search>    <content>===var emailNew: String===    </content>  </change></file><!-- This example fails because the <search> block is only one line and ambiguous. -->-----### Example: Negative Example - Ambiguous Search Block<Plan>Demonstrate an ambiguous search block that can match multiple blocks (e.g., multiple closing braces).</Plan><file path="path/service.swift" action="modify">  <change>    <description>Ambiguous search block with multiple closing braces</description>    <search>===    }}===    </search>    <content>===        foo() {        }    }}===    </content>  </change></file><!-- This example fails because the <search> block is ambiguous due to multiple matching closing braces. -->-----### Example: Full File Rewrite<Plan>Rewrite the entire User file to include an email property.</Plan><file path="Models/User.swift" action="rewrite">  <change>    <description>Full file rewrite with new email field</description>    <content>===import Foundationstruct User {    let id: UUID    var name: String    var email: String    init(name: String, email: String) {        self.id = UUID()        self.name = name        self.email = email    }}===    </content>  </change></file>-----### Example: Create New File<Plan>Create a new RoundedButton for a custom Swift UIButton subclass.</Plan><file path="Views/RoundedButton.swift" action="create">  <change>    <description>Create custom RoundedButton class</description>    <content>===import UIKit@IBDesignableclass RoundedButton: UIButton {    @IBInspectable var cornerRadius: CGFloat = 0}===    </content>  </change></file>-----### Example: Delete a File<Plan>Remove an obsolete file.</Plan><file path="Obsolete/File.swift" action="delete">  <change>    <description>Completely remove the file from the project</description>    <content>======    </content>  </change></file>## Final Notes1. **modify** Always wrap the exact original lines in <search> and your updated lines in <content>, each enclosed by ===.2. **modify** The <search> block must match the source code exactly—down to indentation, braces, spacing, and any comments. Even a minor mismatch causes failed merges.3. **modify** Only replace exactly what you need. Avoid including entire functions or files if only a small snippet changes, and ensure the <search> content is unique and easy to identify.4. **rewrite** Use `rewrite` for major overhauls, and `modify` for smaller, localized edits. Rewrite requires the entire code to be replaced, so use it sparingly.5. You can always **create** new files and **delete** existing files. Provide full code for create, and empty content fordelete. Avoid creating files you know exist already.6. If a file tree is provided, place your files logically within that structure. Respect the user’s relative or absolute paths.7. Wrap your final output in ```XML ... ``` for clarity.8. **Important:** Do not wrap any XML output in CDATA tags (i.e. `<![CDATA[ ... ]]>`). Repo Prompt expects raw XML exactly as shown in the examples.9. **IMPORTANT** IF MAKING FILE CHANGES, YOU MUST USE THE AVAILABLE XML FORMATTING CAPABILITIES PROVIDED ABOVE - IT IS THE ONLY WAY FOR YOUR CHANGES TO BE APPLIED.10. The final output must apply cleanly with no leftover syntax errors.</xml_formatting_instructions>
然後大模型就會生成一段 xml 格式的響應。
<Plan>修改 FileParserServiceImpl 類中的方法簽名和實現邏輯,使其與 FileParserService 介面保持一致。主要變更是將返回型別從 Result<T> 改為直接返回 T,並相應調整異常處理邏輯。</Plan><file path="app/service/src/main/java/com/xxx/medigw/core/service/parser/impl/FileParserServiceImpl.java" action="rewrite">  <change>    <description>更新 FileParserServiceImpl 實現類,使其方法簽名與 FileParserService 介面一致</description>    <content>===package com.xxx.medigw.core.service.parser.impl;import com.xxx.medigw.common.utils.LoggerUtil;import com.xxx.medigw.core.service.ocr.Doc2xOcrService;import com.xxx.medigw.rpc.model.ParserProgress;import com.xxx.medigw.rpc.model.ParserResult;import com.xxx.medigw.rpc.FileParserService;import com.xxx.sofa.runtime.api.annotation.SofaService;import com.xxx.sofa.runtime.api.annotation.SofaServiceBinding;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;/** * 檔案解析服務實現類 * * 主要職責: * 1. 提供檔案解析進度查詢功能 * 2. 提供檔案解析結果獲取功能 * * 本服務作為業務層與底層OCR服務的中間層,提供統一的錯誤處理和日誌記錄 */@ServicepublicclassFileParserServiceImplimplementsFileParserService {/**     * 日誌記錄器     */privatestatic final Logger LOGGER = LoggerFactory.getLogger(FileParserServiceImpl.class);/**     * 文件解析服務     */    @Autowiredprivate Doc2xOcrService doc2xOcrService;/**     * 查詢檔案解析進度     *     * 根據任務ID查詢檔案解析的當前進度狀態,提供統一的錯誤處理     *     * @param taskId 解析任務ID     * @return 解析進度資訊,包含狀態、總進度百分比、詳細進度對映和錯誤資訊(如果有)     * @throws IllegalArgumentException 當引數無效時丟擲     * @throws RuntimeException 當系統發生異常時丟擲     */    @Overridepublic ParserProgress queryParserProgress(String taskId){// 1. 引數校驗if (taskId == null || taskId.isEmpty()) {            LoggerUtil.warn(LOGGER, "查詢解析進度失敗,任務ID不能為空");thrownew IllegalArgumentException("任務ID不能為空");        }try {// 2. 呼叫服務查詢解析進度            ParserProgress progress = doc2xOcrService.queryParserProgress(taskId);// 3. 處理查詢結果if (progress == null) {                LoggerUtil.error(LOGGER, "獲取解析進度失敗,taskId: {0}", taskId);thrownew RuntimeException("獲取解析進度失敗,請稍後重試");            }            LoggerUtil.info(LOGGER, "查詢解析進度成功,taskId={0}, status={1}, progress={2}%",                    taskId, progress.getStatus(), progress.getTotalProgress());// 4. 返回進度資訊return progress;        } catch (Exception e) {// 5. 異常處理            LoggerUtil.error(e, LOGGER, "查詢解析進度異常,taskId: {0}", taskId);thrownew RuntimeException("查詢解析進度異常: " + e.getMessage(), e);        }    }/**     * 獲取檔案解析結果     *     * 根據任務ID獲取檔案解析完成後的結果資料,提供統一的錯誤處理     *     * @param taskId 解析任務ID     * @return 解析結果資料,結構化的ParserResult物件     * @throws IllegalArgumentException 當引數無效時丟擲     * @throws RuntimeException 當系統發生異常時丟擲     */    @Overridepublic ParserResult fetchResult(String taskId){// 1. 引數校驗if (taskId == null || taskId.isEmpty()) {            LoggerUtil.warn(LOGGER, "獲取解析結果失敗,任務ID不能為空");thrownew IllegalArgumentException("任務ID不能為空");        }try {// 2. 呼叫服務獲取解析結果            ParserResult result = doc2xOcrService.fetchResult(taskId);// 3. 處理獲取結果if (result == null) {                LoggerUtil.error(LOGGER, "獲取解析結果失敗,taskId: {0}", taskId);thrownew RuntimeException("獲取解析結果失敗,可能解析尚未完成或解析失敗");            }            LoggerUtil.info(LOGGER, "獲取解析結果成功,taskId={0}, content長度={1}",                    taskId, result.getContent() != null ? result.getContent().length() : 0);// 4. 返回結果資料return result;        } catch (Exception e) {// 5. 異常處理            LoggerUtil.error(e, LOGGER, "獲取解析結果異常,taskId: {0}", taskId);thrownew RuntimeException("獲取解析結果異常: " + e.getMessage(), e);        }    }}===    </content>  </change></file>
將這段 prompt 和響應貼到 Apply 的介面中,就能進行程式碼合併。
點選 Merge Changes,就可以自動合併程式碼。

XML Diff 解釋

接下來我解釋一下這段神奇的 XML Diff 到底做了什麼。這段 prompt 提供了使用 XML 格式指令來控制程式碼編輯助手進行程式碼修改的詳細指南。它定義了助手的角色、能力、可用的工具和操作,以及進行各種程式碼編輯操作(如建立、修改、重寫和刪除檔案)時必須遵循的具體 XML 格式。
1.Role (角色)
  • Code Editing Assistant (程式碼編輯助手):  明確了助手的定位,它是用來編輯程式碼的。
  • 能力: 助手能夠執行編輯請求,並與使用者討論程式碼或其他問題。
  • 輸出格式:  強調助手在回覆時需要提供完整的指令或程式碼行,並使用 XML 格式。
2.Capabilities (能力)
  • Create new files (建立新檔案): 助手可以建立新的程式碼檔案。
  • Rewrite entire files (重寫整個檔案): 助手可以替換現有檔案的全部內容。
  • Perform partial search/replace modifications (執行部分搜尋/替換修改): 助手可以在檔案中查詢特定的程式碼片段並替換它們。
  • Delete existing files (刪除現有檔案): 助手可以刪除檔案。
  • 避免佔位符:  強調輸出的程式碼必須完整,不能使用 或 // existing code here 這樣的佔位符。
3.Tools & Actions (工具 & 操作)
這部分列出了助手可用的具體工具,以及每個工具對應的操作:
  • create:  建立新檔案。如果檔案不存在,則建立它。
  • rewrite:  重寫現有檔案的全部內容。
  • modify (search/replace): 部分修改檔案。使用 <search> 標籤指定要查詢的內容,<content> 標籤指定替換的內容。
  • delete:  刪除檔案。<content> 標籤為空。
4.Format to Follow for Repo Prompt's Diff Protocol (倉庫 Prompt 的 Diff 協議格式)
這是核心部分,詳細說明了 XML 格式的結構:
  • <Plan>:
  • 這是一個標籤,用於描述你的方法或推理。
  • 助手應該在這裡解釋它計劃如何執行請求,例如,它將修改哪些檔案,以及修改的原因。
  • <file path="path/to/example.swift" action="one_of_the_tools">
  • <file>標籤:  用於指定要操作的檔案。
  • path 屬性: 檔案路徑,例如 path/to/example.swift
  • action 屬性:  指定要執行的操作,必須是 createrewritemodifydelete 中的一個。
  • 一個 <file> 標籤代表對一個檔案的操作。
  • <change>
  • <file> 標籤內,使用 <change> 標籤來描述一個具體的修改。一個 <file> 標籤可以包含多個 <change> 標籤,用於對同一個檔案進行多次修改。
  • <description>:  簡要解釋這個特定修改的目的。
  • <search>: (僅用於 modify 操作)
  • 使用 <search> 標籤來包裹要搜尋的程式碼塊。
  • 程式碼塊需要用 === 包裹。
  • 重要:  <search> 塊中的程式碼必須與原始檔案中的程式碼完全匹配,包括縮排、空格、大括號、註釋等。
  • <content>:
  • 使用 <content> 標籤來包裹要替換或新增的程式碼塊。
  • 程式碼塊需要用 === 包裹。
  • 對於 modify 操作,<content> 中的程式碼將替換 <search> 塊找到的程式碼。
  • 對於 createrewrite 操作,<content> 包含檔案的完整內容。
  • 對於 delete 操作,<content> 為空。
  • <!– Add more <change> blocks … –>:  提示可以在同一個 <file> 標籤內新增多個 <change> 標籤來執行多個編輯操作。
5.Tools Demonstration (工具演示)
提供了一些簡單的 XML 示例,演示如何使用不同的工具:
  • <file path="NewFile.swift" action="create">: 建立新檔案 NewFile.swift,檔案內容放在 <content> 標籤中。
  • <file path="DeleteMe.swift" action="delete">: 刪除檔案 DeleteMe.swift<content> 標籤為空。
  • <file path="ModifyMe.swift" action="modify">: 修改檔案 ModifyMe.swift,使用 <search><content> 標籤進行部分替換。
  • <file path="RewriteMe.swift" action="rewrite">: 重寫檔案 RewriteMe.swift,整個新檔案內容放在 <content> 標籤中。
  • <file path="RewriteMe.swift" action="rewrite">: 再次強調 rewrite 操作不需要 <search> 標籤。
6.Format Guidelines (格式指南)
總結了 XML 格式的關鍵規則:
  • <Plan>:  必須以 <Plan> 標籤開始,描述方法。
  • <file> Tag:  使用 <file path="…" action="…"> 標籤,action 必須是可用的工具之一。
  • <change> Tag:  在 <file> 中使用 <change>,提供 <description><content>。對於 modify,還需要 <search>
  • modify: <search> & <content>:  強調 <search><content> 程式碼塊需要用 === 包裹,並且 <search> 塊必須與原始程式碼完全匹配。
  • modify:  對於同一檔案的多個修改,使用多個 <change> 標籤,而不是多個 <file> 標籤。
  • rewrite:  用於大型修改,省略 <search>,整個新檔案內容放在 <content> 中。
  • create:  用於建立新檔案,整個檔案內容放在 <content> 中。
  • delete:  用於刪除檔案,<content> 為空。
7.Code Examples(程式碼示例)
提供了一系列程式碼示例,包括:
  • Search and Replace (Add email property):  演示 modify 操作,新增 email 屬性到 User 結構體。
  • Negative Example – Mismatched Search Block:  展示了 <search> 塊不匹配原始程式碼會導致修改失敗。
  • Negative Example – Mismatched Brace Balance:  展示了 <content> 中括號不平衡會導致問題。
  • Negative Example – One-Line Search Block:  說明單行 <search> 塊可能過於模糊不可靠。
  • Negative Example – Ambiguous Search Block:  說明模糊的 <search> 塊(例如多個匹配的 closing brace)會導致問題。
  • Full File Rewrite:  演示 rewrite 操作,重寫整個 User 檔案以包含 email 屬性。
  • Create New File:  演示 create 操作,建立一個新的 RoundedButton 類。
  • Delete a File:  演示 delete 操作,刪除一個檔案。
8.Final Notes (最終注意事項)
總結了關鍵要點和注意事項:
1)modify:  <search> 和 <content> 都需要用 === 包裹。
2modify:  <search> 必須完全匹配原始程式碼,包括所有細節。
3)modify:  只替換需要修改的部分,<search> 內容應儘可能獨特且易於識別。
4)rewrite:  用於大型修改,modify 用於區域性修改。rewrite 替換整個檔案,謹慎使用。
5)create 和 delete:  可以隨時建立和刪除檔案。create 提供完整程式碼,delete 提供空 <content>避免建立已存在的檔案。
6)檔案路徑:  如果提供檔案樹,檔案應放置在邏輯位置。尊重相對或絕對路徑。
7)XML 包裹:  最終輸出需要用 “XML … “ 包裹,為了清晰。(這可能是一個格式化的約定,在實際應用中可能不需要嚴格遵守 XML 格式本身的要求,而是要確保輸出是 XML 結構)
8)禁止 CDATA:  重要: 不要將 XML 輸出包裹在 CDATA 標籤中 (<![CDATA[ … ]]>)。Repo Prompt 期望的是原始 XML。
9)強制 XML 格式: 重要: 如果要進行檔案更改,必須使用提供的 XML 格式,這是應用更改的唯一方式。
10)語法正確:  最終輸出必須能夠乾淨地應用,並且不能留下語法錯誤。
總結
AI 編碼提升的效率跟程式設計師的水平是正相關的,現在的 AI 有點像一個實習生,你必須要把想讓它做的事情寫的很清楚,如果你自己都不知道要如何實現一個功能,AI 就更不知道了。當然開發過程裡面還有一大部分的時間是在除錯上面,這塊要讓 AI 幫忙也需要提問者有足夠的知識儲備。所以我們也不能期望過高,這樣你才能有耐心好好帶帶這位實習生。
隨著 AI 能力的進步,很多人覺得 AI 很快就能取代我們這些程式設計師,他們覺得產品經理可以直接繞過程式設計師,讓 AI 直接開發出他們想要的產品。作為一名有 8 年工作經驗,在手工程式設計古典時代成長起來的程式設計師,我其實並不擔心這個。
寫程式碼本身其實不算是很大的工作量,一旦你熟悉了語法和中介軟體,寫程式碼確實跟搬磚區別不大。我們大部分時間主要是花在解決環境問題,跟上下游聯調,跟產品一起澄清需求這些事情上面。即使 AI 能夠幫我們完成全鏈路的聯調,也取代不了程式設計師。
首先,知道應該修改哪一行程式碼,比知道如何修改這行程式碼值錢一千倍。其次,開發軟體真正的難點不在於寫程式碼,而是在於定義清楚問題。最後,當 AGI 真的出現的時候,我覺得應該討論的不是程式設計師會不會被取代的問題,而是應該擔心碳基生物如何跟矽基生物共存了。
雲原生企業級資料湖
基於物件儲存 OSS 構建的資料湖,可對接多種資料輸入方式,儲存任何規模的結構化、半結構化、非結構化資料,打破資料湖孤島。    
點選閱讀原文檢視詳情。

相關文章