关于用大型语言模型(LLM)帮忙写代码的网上讨论,总会有一些开发者跳出来说他们的体验很糟糕。他们常常问自己到底哪里做错了——为什么有些人说效果特别好,而他们自己试了却发现问题一大堆?
用LLM写代码其实挺难的,也不那么直观。要想搞清楚怎么用它、什么时候用它好,得花不少功夫,而且现在也没什么指南能帮大家弄明白怎么用才最有效。
如果有人告诉你,用LLM编程很简单,那他们可能(无意中)误导了你。他们可能碰巧发现了一些好用的方法,但这些方法并不是每个人都能轻松掌握的。
我用了两年多的LLM,收获挺大。下面我试着把我的一些经验和直觉分享给你。
上下文是关键Context is King
要想从LLM那里得到好结果,大部分技巧都跟管理它的上下文Context有关——也就是你当前对话中的文字内容。
这个上下文不仅仅是你输入的那条提示:成功的LLM互动通常是以对话的形式进行的,上下文包括你发的每一条消息和LLM回复的每一条内容,它们都存在于当前的对话线索中。
当你开始一个新对话时,上下文会被重置为零。这一点很重要,因为当一个对话变得没用了,通常的解决办法就是清空对话,重新开始。
有些LLM编程工具不仅仅局限于对话。比如Claude Projects,它允许你预先加载大量文本作为上下文——最近还增加了直接从GitHub仓库导入代码的功能,我用得很多。
像Cursor和VS Code Copilot这样的工具会自动包含你当前编辑会话和文件布局的上下文,有时候你还可以用Cursor的@commands这样的机制来引入额外的文件或文档。
我之所以主要直接用ChatGPT和Claude的网页或应用界面,是因为这样更容易让我清楚地知道上下文里到底有什么。那些不让我清楚看到上下文的LLM工具,效果就没那么好。
你可以利用之前回复也是上下文的一部分这一点来帮你。对于复杂的编码任务,可以先让LLM写个简单版本,检查它是否能运行,然后再一步步迭代,构建更复杂的实现。
我经常通过丢进去一些已有的代码来开启一个新对话,然后和LLM一起修改它。
我最喜欢的代码提示技巧是,丢进去几个和我想要构建的东西相关的完整例子,然后提示LLM用它们作为灵感来做一个新项目。我在描述我的JavaScript OCR应用时详细写过这一点,那个应用结合了Tesseract.js和PDF.js——这两个库我以前用过,所以我可以在提示中提供可运行的例子。
“感觉式编程”是个好学习方法
大概一个多月前,Andrej Karpathy发明了一个词叫“感觉式编程”(vibe-coding),这个词现在挺火的:
“有一种新的编程方式,我称之为‘感觉式编程’,就是你完全跟着感觉走,拥抱指数级变化,甚至忘了代码的存在。[...]我会问一些特别傻的问题,比如‘把侧边栏的内边距减半’,因为我懒得去找。我总是‘全部接受’,不再看差异。当我收到错误信息时,我就直接复制粘贴进去,不加任何注释,通常这样就能解决问题。”
Andrej说这“对于周末的临时项目来说还不错”。这也是探索这些模型能力的好方法——而且特别有趣。
学习LLM最好的方式就是玩它们。把荒谬的想法丢给它们,然后用感觉式编程直到它们勉强能工作,这确实是一个加速你建立直觉的好方法,让你知道什么有用、什么没用。
在Andrej给这种感觉式编程命名之前,我就已经在这么做了!我的simonw/tools GitHub仓库里有77个HTML+JavaScript应用和6个Python应用,每一个都是通过提示LLM构建的。我从构建这个集合中学到了很多,而且我每周都会增加几个新原型。
你可以在tools.simonwillison.net上直接试用我的大部分应用——这是GitHub Pages发布的版本。我在十月份写过一些更详细的笔记,记录了这周我用Claude Artifacts构建的所有东西。
如果你想看每个应用的聊天记录,几乎都可以在提交历史中找到链接——或者访问新的colophon页面,那里有一个包含所有链接的索引。
banq注:
主要是这两点,其他点击标题见原文,个人以为使用Grok3/DeepSeek这种深度思考方式,它会逐步提示你分析过程:
1、例如我给出提示:
Cache-Control: max-age=3600 Last-Modified: Mon, 10 Mar 2025 22:42:08 GMT Expires: Tue, 11 Mar 2025 00:34:47 GMT 这个时间设置对吗?
|
Grok3巴拉巴拉分析半天:
我们来分析一下这些 HTTP 头字段的时间设置,看看是否有问题。 给定的头字段: Cache-Control: max-age=3600 表示资源可以缓存 3600 秒(即 1 小时),从响应生成或验证的时间开始计算。
Last-Modified: Mon, 10 Mar 2025 22:42:08 GMT 表示资源最后修改的时间是 2025 年 3 月 10 日 22:42:08(GMT)。
Expires: Tue, 11 Mar 2025 00:34:47 GMT 表示资源的过期时间是 2025 年 3 月 11 日 00:34:47(GMT),在此时间之前,缓存被认为是新鲜的。 问题分析:时间设置是否合理? 1. Cache-Control 和 Expires 的关系 Cache-Control: max-age=3600 优先级高于 Expires,根据现代 HTTP 规范(RFC 7234),如果两者都存在,max-age 通常会覆盖 Expires。
max-age=3600 表示缓存新鲜时间是 1 小时。
Expires 给出的时间是 2025 年 3 月 11 日 00:34:47,而 Last-Modified 是 2025 年 3 月 10 日 22:42:08。 我们计算一下 Expires 和 Last-Modified 之间的时间差: Last-Modified: 2025-03-10 22:42:08
Expires: 2025-03-11 00:34:47
时间差:从 22:42:08 到 00:34:47(次日),总共是 1 小时 52 分 39 秒(即 6759 秒)。 2. 问题:max-age 和 Expires 不一致 max-age=3600 表示缓存有效期是 3600 秒(1 小时)。
但 Expires 和 Last-Modified 的时间差是 6759 秒(约 1 小时 52 分),远远大于 max-age 定义的 3600 秒。
根据 HTTP 规范,如果 max-age 和 Expires 同时存在,max-age 优先。但这种不一致可能会让客户端(尤其是老旧的实现)感到困惑。 3. 假设响应的生成时间 HTTP 头的 Last-Modified 并不是响应的生成时间,但通常可以用作参考点。
如果假设响应生成时间接近 Last-Modified(例如 2025-03-10 22:42:08),那么: 根据 max-age=3600,缓存应该在 2025-03-10 23:42:08 过期。
但 Expires 却指定了一个更晚的时间(2025-03-11 00:34:47),这会导致歧义。 结论:时间设置有问题 不一致性:max-age=3600 和 Expires 之间的有效期不匹配。 max-age=3600 意味着缓存有效期是 1 小时。
Expires 暗示的有效期是 6759 秒(约 1 小时 52 分)。
这会导致客户端在选择缓存策略时可能出现混乱。 建议修正: 如果你想使用 max-age=3600,那么 Expires 应该设置为响应生成时间(或 Last-Modified 时间)加上 3600 秒。 假设响应生成时间接近 Last-Modified(2025-03-10 22:42:08),那么 Expires 应该是: 2025-03-10 22:42:08 + 3600 秒 = 2025-03-10 23:42:08 GMT。 或者,如果想以 Expires 的时间为准(2025-03-11 00:34:47),那么 max-age 应该设置为 6759 秒。 修正后的示例 如果优先 max-age=3600: Cache-Control: max-age=3600 Last-Modified: Mon, 10 Mar 2025 22:42:08 GMT Expires: Mon, 10 Mar 2025 23:42:08 GMT
如果优先 Expires 时间: Cache-Control: max-age=6759 Last-Modified: Mon, 10 Mar 2025 22:42:08 GMT Expires: Tue, 11 Mar 2025 00:34:47 GMT
总结 当前设置不合理,因为 max-age 和 Expires 的有效期不一致。建议根据实际需求选择一个为主(通常推荐 max-age),并让两者保持一致,以避免客户端行为不明确。
|
2、然后我给出生成这个http头部信息的Java代码:
这是JSP页面头部,用Java设置的代码:public static boolean checkHeaderCache(long adddays, long modelLastModifiedDate, HttpServletRequest request, HttpServletResponse response) {
if (request.getAttribute("myExpire") != null) { System.err.print(" checkHeaderCache called above twice times :" + request.getRequestURI()); return true; } request.setAttribute("myExpire", adddays);
// convert seconds to ms. try {
// if over expire data, see the Etags; // ETags if ETags no any modified String etag = request.getHeader("If-None-Match");
if (etag != null) { if (etag.equals(Long.toString(modelLastModifiedDate))) { // not modified response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return false; } } else { long header = request.getDateHeader("If-Modified-Since"); if (header > 0) { if (modelLastModifiedDate <= header || (modelLastModifiedDate - header) < 1000) { // during the period not happend modified response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return false; } } } // if th model has modified , setup the new modified date setEtagHaeder(response, modelLastModifiedDate); setRespHeaderCache(adddays, modelLastModifiedDate, request, response); } catch (Exception e) { e.printStackTrace(); } return true; }
|
Grok3又不辞辛苦分析加总结:
我来帮你分析这段 JSP 页面的缓存控制代码,结合你之前提到的 HTTP 头信息,找出问题并提供改进建议。代码的主要功能是处理 HTTP 缓存控制,通过 ETag 和 If-Modified-Since 检查资源是否修改,并设置响应头(如 Cache-Control, Last-Modified, Expires 等)。 代码功能分析 这段代码实现了标准的 HTTP 缓存验证逻辑,主要步骤如下: 检查重复调用:通过 request.getAttribute("myExpire") 避免重复调用。 ETag 验证:检查 If-None-Match 头,如果客户端发送的 ETag 与服务器端的 modelLastModifiedDate 匹配,返回 304 Not Modified。 Last-Modified 验证:检查 If-Modified-Since 头,如果资源未修改(或修改时间差小于 1 秒),返回 304 Not Modified。 设置响应头: 如果资源有修改,调用 setEtagHaeder 设置新的 ETag。 调用 setRespHeaderCache 设置缓存相关头(如 Cache-Control, Last-Modified, Expires)。 与之前提到的 HTTP 头对应 你之前提到的 HTTP 头: Cache-Control: max-age=3600 Last-Modified: Mon, 10 Mar 2025 22:42:08 GMT Expires: Tue, 11 Mar 2025 00:34:47 GMT 这些头很可能是由 setRespHeaderCache 方法设置的。我们推测 setRespHeaderCache 方法会根据 adddays 和 modelLastModifiedDate 参数设置这些头。 之前分析过,这些头的时间设置不一致(max-age=3600 表示 1 小时,但 Expires 和 Last-Modified 的时间差是 6759 秒,约 1 小时 52 分)。问题可能出在 setRespHeaderCache 方法的实现上。 问题分析 从代码逻辑来看,当前代码在验证缓存方面没有明显问题,但结合你提供的 HTTP 头,可能存在以下问题: 1. ETag 和 Last-Modified 的逻辑 ETag:代码直接将 modelLastModifiedDate 转换为字符串作为 ETag。这种方式简单,但不够健壮。ETag 通常需要包含更多信息(如文件内容哈希),以更准确地标识资源是否变化。 If-Modified-Since:代码比较了 modelLastModifiedDate 和 If-Modified-Since,并允许 1 秒的误差(< 1000)。这种误差容忍可能是为了处理时间精度问题,但可能导致缓存不一致的风险(例如,资源刚好在 1 秒内修改,却被误判为未修改)。 2. setRespHeaderCache 方法的问题 你没有提供 setRespHeaderCache 方法的具体实现,但根据输出的 HTTP 头(max-age=3600 和 Expires 不一致),可能存在以下问题: max-age 和 Expires 不匹配:max-age=3600 表示缓存 1 小时,但 Expires 对应的时间差是 6759 秒。这种不一致可能是因为: adddays 参数被误解或计算错误。 Expires 头的计算逻辑与 max-age 不一致。 adddays 参数的作用不明确:从方法签名看,adddays 应该是缓存的有效期(单位可能是天或秒)。如果单位是天,但代码误认为是秒,就会导致时间设置错误。 3. 时间精度问题 modelLastModifiedDate 是以毫秒为单位的(Java 中的时间戳通常如此),但 HTTP 头的 Last-Modified 和 Expires 是以秒为单位的。代码中可能没有正确处理这种精度转换,导致时间差不准确。 改进建议 以下是针对代码和 HTTP 头的改进建议: 1. 修正 setRespHeaderCache 方法 假设 setRespHeaderCache 方法的实现类似于以下逻辑: java private static void setRespHeaderCache(long adddays, long modelLastModifiedDate, HttpServletRequest request, HttpServletResponse response) { // 设置 Cache-Control response.setHeader("Cache-Control", "max-age=" + adddays);
// 设置 Last-Modified response.setDateHeader("Last-Modified", modelLastModifiedDate);
// 设置 Expires long expires = System.currentTimeMillis() + (adddays * 1000); // 假设 adddays 是秒 response.setDateHeader("Expires", expires); } 问题: 如果 adddays 是 3600(秒),那么 max-age=3600 是正确的,但 Expires 可能是基于当前时间计算的,导致与 Last-Modified 的时间差不等于 3600 秒。 你提供的 Expires 是 2025-03-11 00:34:47,可能是因为 System.currentTimeMillis() 的值比 modelLastModifiedDate 晚了 3159 秒。 改进: 确保 Expires 和 max-age 一致。Expires 应该基于 Last-Modified 或响应生成时间加上 max-age。 假设 adddays 是缓存的秒数,修正后的实现如下: java private static void setRespHeaderCache(long adddays, long modelLastModifiedDate, HttpServletRequest request, HttpServletResponse response) { // 设置 Cache-Control response.setHeader("Cache-Control", "max-age=" + adddays);
// 设置 Last-Modified response.setDateHeader("Last-Modified", modelLastModifiedDate);
// 设置 Expires,基于 Last-Modified 时间加上 max-age(单位:毫秒) long expires = modelLastModifiedDate + (adddays * 1000); response.setDateHeader("Expires", expires); } 如果 adddays = 3600,modelLastModifiedDate = 2025-03-10 22:42:08 GMT(即 1741560128000 毫秒),那么: Expires = 1741560128000 + (3600 * 1000) = 1741563728000 毫秒 转换为日期:2025-03-10 23:42:08 GMT,与 max-age=3600 一致。 2. 改进 ETag 生成 当前代码将 modelLastModifiedDate 直接作为 ETag,不够健壮。建议使用更强的 ETag,例如结合文件内容的哈希值: java private static void setEtagHaeder(HttpServletResponse response, long modelLastModifiedDate) { // 假设有某种方式获取资源内容的哈希值 String contentHash = computeContentHash(); // 伪代码,需根据实际情况实现 String etag = "\"" + contentHash + "-" + modelLastModifiedDate + "\""; response.setHeader("ETag", etag); }
private static String computeContentHash() { // 伪代码:计算资源的哈希值(例如 MD5 或 SHA-1) return "some-hash-value"; } 然后在 checkHeaderCache 中比较 ETag 时,也需要解析并验证: java String etag = request.getHeader("If-None-Match"); if (etag != null) { String expectedEtag = "\"" + computeContentHash() + "-" + modelLastModifiedDate + "\""; if (etag.equals(expectedEtag)) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return false; } } 3. 处理时间精度 HTTP 头的时间精度是秒,但 Java 的时间戳是毫秒。建议在设置和比较时间时,统一处理精度: java long header = request.getDateHeader("If-Modified-Since"); if (header > 0) { // 将时间戳对齐到秒,避免毫秒级误差 long modelLastModifiedInSeconds = modelLastModifiedDate / 1000 * 1000; long headerInSeconds = header / 1000 * 1000; if (modelLastModifiedInSeconds <= headerInSeconds) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return false; } } 4. 明确 adddays 的单位 adddays 参数的命名容易让人误解为“天”,但从 max-age=3600 来看,单位应该是秒。 建议改名为 maxAgeSeconds 并在文档中明确说明: java public static boolean checkHeaderCache(long maxAgeSeconds, long modelLastModifiedDate, HttpServletRequest request, HttpServletResponse response) 5. 避免硬编码误差容忍 当前代码允许 1 秒的误差(modelLastModifiedDate - header < 1000),但这可能不适合所有场景。建议将误差容忍作为参数传入,或者移除硬编码,直接比较时间戳。 修正后的完整代码 以下是改进后的 checkHeaderCache 方法和相关方法: java public static boolean checkHeaderCache(long maxAgeSeconds, long modelLastModifiedDate, HttpServletRequest request, HttpServletResponse response) { if (request.getAttribute("myExpire") != null) { System.err.println("checkHeaderCache called above twice times: " + request.getRequestURI()); return true; } request.setAttribute("myExpire", maxAgeSeconds);
try { // ETag 验证 String etag = request.getHeader("If-None-Match"); if (etag != null) { String expectedEtag = "\"" + modelLastModifiedDate + "\""; // 简化版,实际应包含内容哈希 if (etag.equals(expectedEtag)) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return false; } }
// If-Modified-Since 验证 long header = request.getDateHeader("If-Modified-Since"); if (header > 0) { // 精确比较,避免误差 if (modelLastModifiedDate <= header) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return false; } }
// 设置新的响应头 setEtagHaeder(response, modelLastModifiedDate); setRespHeaderCache(maxAgeSeconds, modelLastModifiedDate, request, response); } catch (Exception e) { e.printStackTrace(); } return true; }
private static void setEtagHaeder(HttpServletResponse response, long modelLastModifiedDate) { response.setHeader("ETag", "\"" + modelLastModifiedDate + "\""); }
private static void setRespHeaderCache(long maxAgeSeconds, long modelLastModifiedDate, HttpServletRequest request, HttpServletResponse response) { // 设置 Cache-Control response.setHeader("Cache-Control", "max-age=" + maxAgeSeconds);
// 设置 Last-Modified response.setDateHeader("Last-Modified", modelLastModifiedDate);
// 设置 Expires,基于当前时间加上 max-age long expires = modelLastModifiedDate + (maxAgeSeconds * 1000); response.setDateHeader("Expires", expires); } 修正后的 HTTP 头输出 假设: maxAgeSeconds = 3600(1 小时) modelLastModifiedDate = 2025-03-10 22:42:08 GMT(1741560128000 毫秒) 修正后的 HTTP 头将是: Cache-Control: max-age=3600 Last-Modified: Mon, 10 Mar 2025 22:42:08 GMT Expires: Mon, 10 Mar 2025 23:42:08 GMT ETag: "1741560128000" 这样,max-age 和 Expires 就一致了,符合 HTTP 规范。
总结 当前代码的逻辑基本正确,但 setRespHeaderCache 方法可能导致 max-age 和 Expires 不一致。 建议明确 adddays 参数的单位,确保 Expires 和 max-age 一致。 改进 ETag 生成方式,增强缓存验证的准确性。 处理时间精度问题,避免毫秒和秒之间的误差。 如果有 setRespHeaderCache 方法的具体实现,可以进一步分析和优化。
|
3、我按照Grok3建议,修改代码后,将Http头部结果再发给它,果然符合Grok3预取,他一点都不骄傲地说修改符合预期,然后又给出进一步方案,我觉得已经符合我的预期,没有深挖下去,我的思路还没有与它同步深入。
这段代码重构见这里:ToolsUtil.java的checkHeaderCache方法。
老程序员靠DeepSeek/Grok3之类深度思考模型又能续命了,关键是树立上下文为王的理念!