关于用大型语言模型(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又不辞辛苦分析加总结:
代码功能分析
这段代码实现了标准的 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方法。
关于Context重要性见我原创:Context与Content一字之差暗藏惊天玄机
805

被折叠的 条评论
为什么被折叠?



