AI自动吐代码:Context决定成败


关于用大型语言模型(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之类深度思考模型又能续命了,关键是树立上下文为王的理念!