TDD第一步就卡住了

写完小工具,这一篇回来咱们接着写咱们的程序。再看一眼咱们的程序运转视图:

ChatGPT编程秀-7: 别拿编程语言不当语言

带着TDD思路,我进入了 ejs_and_yaml_dsl_loader 这个模块,这块由于我切的不是很好,所以这代码有点难写,不过不要紧,正好咱们实践工作大部分的场景都是这样的。看看咱们在这儿能玩出点什么来。

那么这次的需求呢是这个样子的,咱们需求把ejs模版引擎渲染出的yaml转换为json,那么咱们这个功用会十分杂乱,所以咱们没有以上来就去做ejs的部分。而是先从yaml的部分下手。假如仅仅yaml转json的话其实没什么意思,一行代码就完成了,关键是yaml里面有一个语法叫做json指针

这个东西很重要。咱们之前画的那个图仍是有一点点。简化的咱们只画了竖向的分层。可是在横向上,假如你要完成像咱们说的,依照规模进行分解的话,假如一个case比较简略的话,还比较好办。一般咱们的case会变得越来越杂乱。或许就需求把它拆成多个。DSL文件,然后最终经过一个dsl把它组装起来。为了完成这种组装的能力呢,咱们就要支撑相似的语法,那么压帽里的jason指针的刚好是这样一个很好用的语法。当然他有他的一些缺陷,他或许不支撑动态的参数,这个是咱们后边的扩展。详细的json指针语法什么样的?一会大家看咱们的prompt就知道了。

之所以需求这个语法以及它的扩展语法,是由于还记得咱们说的咱们需求一个派发引擎吗? 其实派发与组合不过是动态和静态的区别。派发引擎也需求一套组合逻辑。而咱们现在做这个功用便是为未来咱们或许能完成一个派发引擎积累一些经历。

而咱们运用的库呢,他不是原生支撑JSON指针。他不支撑呢,也挺好,由于咱们后边会扩展这个功用。假如他支撑的话,咱们自界说的语法扩展起来反而很费事。背景知识便是这些,咱们开端完成,下面试提问prompt:

我要用nodejs完成一个yaml的loader函数,能够读入yaml转为json,并且能够将其间JSON指针语法转换为详细的值.

这是base.yml:

- item:
    $ref: "/refed.yml#simple_value_keya"
- item:
    keya: 
    $ref: "/refed.yml#simple_value_keya"

这是在同一途径的refed.yml

simple_value_keya: value_of_keya

两个yaml文件都在一个文件夹里,比方叫yaml
而js文件与yaml文件夹同级

我期望的输出为:

[{
    "item":"value_of_keya"
},{
"item":
        {
            "keya": "value_of_keya"
        }
}]

我还期望:

  1. 这个函数是async的。
  2. 读取文件的fs,要运用const fs = require(‘fs/promise’)引进。
  3. 用js-yaml库解析yaml。
  4. 解析JSON指针的进程需求被封装为一个专门的函数。
    代码要怎样完成?(直接给出最终的代码就能够)

成果给我生成了很长的代码:

const fs = require('fs/promises');
const yaml = require('js-yaml');
/**
* 从JSON指针中获取对应的值
* @param {Object} obj - 要获取值的目标
* @param {string} pointer - JSON指针
* @returns {any} 对应的值
*/
function getValueByPointer(obj, pointer) {
const parts = pointer.split('/').slice(1); // 去掉开头的'#'字符,然后拆分为途径的数组
return parts.reduce((acc, cur) => acc[cur], obj); // 顺次按途径访问目标特色
}
/**
* 读取指定途径的YAML文件,并解析其间的JSON指针
* @param {string} path - YAML文件的途径
* @returns {Promise<any>} 解析后的JSON目标
*/
async function loadYamlFile(path) { 
const content = await fs.readFile(path, 'utf8'); // 读取文件内容
const data = yaml.load(content); // 解析YAML为JSON目标
// 遍历JSON目标,查找$ref特色
const processRef = async (obj) => {
    if (typeof obj === 'object' && obj !== null) { // 假如是目标
    if ('$ref' in obj) { // 假如包含$ref特色
        const refPath = obj.$ref.split('#')[0]; // 获取引证文件途径
        const pointer = obj.$ref.split('#')[1]; // 获取JSON指针
        const refData = await loadYamlFile(refPath); 
        const value = getValueByPointer(refData, `#${pointer}`); // 获取引证文件中对应JSON指针的值
        return value;
    } else { // 假如不包含$ref特色,递归处理子特色
        for (const prop in obj) {
        obj[prop] = await processRef(obj[prop]);
        }
    }
    }
    return obj;
};
return await processRef(data); // 处理JSON目标中的$ref特色
}
// 调用loadYamlFile函数并输出成果
(async () => {
const data = await loadYamlFile('./yaml/base.yml');
console.log(JSON.stringify(data, null, 2));
})();

不出意外的,这个代码他是错的,我让他反复生成了很多次,成果总是差那么点,说真实的,差那么点我自己手改也不是不能改,可是输赢欲起来了。不能完全生成这个事仍是有点让我感觉不爽,考虑到假如真的在实践场景中运用的话,这50行的长度也写不了太杂乱的东西,可是一到这个长度,就无法细调整了太丧命了,这又不是人,我不能指着这一行跟他说,这儿有问题……嗯?指着这一行?我顿时灵光一闪。

破门而入,针对性反应

在日常开发中,咱们是怎样经过文字指出某一行有问题呢?答案再显着不过了:经过注释。并且,下面两个注释已经是约定俗成的运用方法了:

  • // TODO: 表明后续需求新增的功用,或其他任务项
  • // FIXME: 表明这儿有Bug要修复

运用这两个注释,你乃至不必过多解说,你只需告知他你用这个注释标了出来,让他按要求批改即可。

所以我就这么给他说:

下面代码不能满足我的需求,我会用//TODO: 来符号问题所在行

const fs = require('fs/promises');
const yaml = require('js-yaml');
/**
* 从JSON指针中获取对应的值
* @param {Object} obj - 要获取值的目标
* @param {string} pointer - JSON指针
* @returns {any} 对应的值
*/
function getValueByPointer(obj, pointer) {
const parts = pointer.split('/').slice(1); // 去掉开头的'#'字符,然后拆分为途径的数组
return parts.reduce((acc, cur) => acc[cur], obj); // 顺次按途径访问目标特色
}
/**
* 读取指定途径的YAML文件,并解析其间的JSON指针
* @param {string} path - YAML文件的途径
* @returns {Promise<any>} 解析后的JSON目标
*/
async function loadYamlFile(path) { //TODO: 这个path参数需求作为递归读取时的的workdir,详细需求见下一个TODO
const content = await fs.readFile(path, 'utf8'); // 读取文件内容
const data = yaml.load(content); // 解析YAML为JSON目标
// 遍历JSON目标,查找$ref特色
const processRef = async (obj) => {
    if (typeof obj === 'object' && obj !== null) { // 假如是目标
    if ('$ref' in obj) { // 假如包含$ref特色
        const refPath = obj.$ref.split('#')[0]; // 获取引证文件途径
        const pointer = obj.$ref.split('#')[1]; // 获取JSON指针
        const refData = await loadYamlFile(refPath); //TODO: refPath 一般是相关于当前yaml文件的,需求拼接一个指定的前缀作才是正确的途径,这个前缀一般是path参数自己的directory途径前缀,比方"./yaml/base.yaml"则是"./yaml"
        const value = getValueByPointer(refData, `#${pointer}`); // 获取引证文件中对应JSON指针的值
        return value;
    } else { // 假如不包含$ref特色,递归处理子特色
        for (const prop in obj) {
        obj[prop] = await processRef(obj[prop]);
        }
    }
    }
    return obj;
};
return await processRef(data); // 处理JSON目标中的$ref特色
}
// 调用loadYamlFile函数并输出成果
(async () => {
const data = await loadYamlFile('./yaml/base.yml');
console.log(JSON.stringify(data, null, 2));
})();

上面代码,这一行写错了:
const refPath = path.resolve(workdir, obj.$ref.split(‘#’)[0]); // 获取引证文件途径
应该用path.join

给我完好的代码

经过这样的方法,我开始到了我想要的代码。之所以用TODO而不必FIXME,是由于我站在他的视角,把这个场景界说为了扩展新功用,而不是修bug。说某一行写错那个确实应该用FIXME,可是我怕混在一起会出错,并且后边已经有注释了,我不知道删掉好不好,毕竟注释也是帮他了解我代码的一种方法,所以单拎出来说了。

这个玩法不见得要那么精准,有的时分或许代码太杂乱了,你或许看不出来哪里有Bug,毕竟不是你写的,也很正常,可是经过调试或许失利的测验数据,你能收敛到一个函数或许一个代码块,也能够经过这个方法来编写prompt。

比方下面这个:

给下面代码种FIXME符号的函数处有一些bug:

module.exports = function render_my_yaml_dsl(data){
    const vm = require('vm');
    // 界说一个函数用于渲染字符串模板
    function render_string_template(originalScriptTemplate, intention) {
        const execScriptTemplate = "`" + originalScriptTemplate + "`";
        const script = new vm.Script(execScriptTemplate);
        const execScript = script.runInNewContext(intention);
        // console.debug(execScript);
        return execScript;
    }
    // 界说一个函数用于打开特色组合
    function expand_attributes(attributes) { // FIXME: 这个函数有bug
        const result = [];
        attributes.forEach(obj => {
            Object.values(obj).forEach(val => {
                if (Array.isArray(val)) {
                    val.forEach(v => {
                        result.push({ ...obj, [Object.keys(obj).find(key => obj[key] === val)]: v });
                    });
                } else {
                    result.push(obj);
                }
            });
        });
        return result;
    }
    const polys = [];
    for (const poly of data.poly) {
        const { template_prompt, ...other } = poly;
        const { template, meta } = template_prompt;
        const variableGroups = expand_attributes(meta);
        const prompts = variableGroups.map(variables => render_string_template(template, variables));
        for (const prompt of prompts) {
            polys.push({ prompt, ...other });
        }
    }
    const result = polys.map(poly => ({ ...data.base, ...poly }));
    return result;
}

给定的输入: //….省掉json数据

期望的输出应该是: //….省掉json数据

而实践上是把数组里仅有的元素重复了8遍, 我需求批改这个错误,但同时,关于给定的输入: //….省掉json数据

我还期望得到输出 //….省掉json数据

结合这两个需求,请问代码哪里有错误?代码应该怎样写?

上面我省掉掉了输入输出的数据内容,便利大家聚集于我在代码上符号了FIXME(看,这个prompt对人也工作^_^)。这儿是我写完ejs_and_yaml_dsl_loader之后,把一切代码集成起来运转的时分发现了一个bug,可是我也不知道bug是什么?(其实是懒得去看,用这个玩意久了,人真的是会变懒)所以我直接让他告知我Bug是什么,然后怎样改,我来评价它的方案,在评价方案的进程中我就搞了解了bug是什么。这个功率可高了很多。

这个技巧不只能够用于编程范畴。在编程范畴呢,咱们取了个巧,刚好有注释和两个配合注释运用的关键字以表达咱们的目的,这些东西咱们不必告知ChatGPT,它已经学习写程序的时分学过了。假如是在编程之外,或许咱们要教他咱们运用的符号是什么意思。 比方,我前一阵跟朋友们评论要规划一个ChatGPT编程秀,在今日这个年代了,咱们就想让ChatGPT帮咱们规划,所以咱们问:

我预备进行一个ChatGPT编程秀,给大家展现怎样用ChatGPT进行编程,时刻为一个小时,可不能够给我一个编程秀的规划,并且附上时刻安排.

所以我得到了下面的输出:

ChatGPT 编程秀规划
阶段 1:介绍 ChatGPT(10 分钟)
介绍 ChatGPT,包含其根本原理、运用场景、优缺点等。
演示怎么运用 OpenAI API 调用 ChatGPT。
以一个简略的比方阐明 ChatGPT 的运用,比方生成一句话。
阶段 2:生成文本(20 分钟)
介绍怎么运用 ChatGPT 生成文本,包含怎么操控生成文本的长度、样式等。
演示怎么用 ChatGPT 生成一篇文章,比方一篇新闻稿。
演示怎么用 ChatGPT 生成一组对话,比方模拟一次客服对话。
阶段 3:生成代码(20 分钟)
介绍怎么运用 ChatGPT 生成代码,包含怎么操控生成代码的杂乱度、风格等。
演示怎么用 ChatGPT 生成一段简略的代码,比方一个打印 “Hello, World!” 的程序。
演示怎么用 ChatGPT 生成一段杂乱的代码,比方一个排序算法的完成。
阶段 4:Q&A 互动(10 分钟)
与观众进行问答互动环节,答复关于 ChatGPT 的任何问题。
总结(5 分钟)
扼要总结 ChatGPT 的特色和运用场景。
感谢观众的收看。

很显着,咱们是个编程秀,所以咱们不需求阶段2,所以我说:

这是一个编程秀,所以只需规划编程的环节就能够了

然后他回复:

ChatGPT 编程秀规划
阶段 1:介绍 ChatGPT(5 分钟)
简略介绍 ChatGPT 的根本原理和运用场景。
阶段 2:生成文本(20 分钟)
演示怎么运用 ChatGPT 生成一段文章,比方一篇新闻稿。
演示怎么运用 ChatGPT 生成一组对话,比方模拟一次客服对话。
阶段 3:生成代码(25 分钟)
演示怎么运用 ChatGPT 生成一段简略的代码,比方一个打印 “Hello, World!” 的程序。
演示怎么运用 ChatGPT 生成一段杂乱的代码,比方一个排序算法的完成。
阶段 4:优化代码生成(10 分钟)
介绍怎么调整 ChatGPT 的参数来优化生成的代码质量。
演示怎么优化前面演示的代码生成。
阶段 5:Q&A 互动(10 分钟)
与观众进行问答互动环节,答复关于 ChatGPT 的任何问题。
总结(5 分钟)
扼要总结 ChatGPT 的特色和运用场景。
感谢观众的收看。

能够看到,并没有删去,这或许是大多数人在面对对长文本进行反应时都会面对的为难了,驾御不住ChatGPT,十分的难过。 所以我这样做:

我会用代码注释的方法在内容上符号给你反应 ,比方”// FIXME: “表明这儿有问题,冒号后边会跟从我要批改的目的。下面是我的输入:
ChatGPT 编程秀规划
阶段 1:介绍 ChatGPT(5 分钟)
简略介绍 ChatGPT 的根本原理和运用场景。
阶段 2:生成文本(20 分钟) // FIXME: 我不需求这个阶段2,
演示怎么运用 ChatGPT 生成一段文章,比方一篇新闻稿。
演示怎么运用 ChatGPT 生成一组对话,比方模拟一次客服对话。
阶段 3:生成代码(25 分钟)
演示怎么运用 ChatGPT 生成一段简略的代码,比方一个打印 “Hello, World!” 的程序。
演示怎么运用 ChatGPT 生成一段杂乱的代码,比方一个排序算法的完成。
阶段 4:优化代码生成(10 分钟)
介绍怎么调整 ChatGPT 的参数来优化生成的代码质量。
演示怎么优化前面演示的代码生成。
阶段 5:Q&A 互动(10 分钟)
与观众进行问答互动环节,答复关于 ChatGPT 的任何问题。
总结(5 分钟)
扼要总结 ChatGPT 的特色和运用场景。
感谢观众的收看。

就得到了这样的输出:

ChatGPT 编程秀规划
阶段 1:介绍 ChatGPT(5 分钟)
简略介绍 ChatGPT 的根本原理和运用场景。
阶段 2:生成代码(35 分钟)
演示怎么运用 ChatGPT 生成一段简略的代码,比方一个打印 “Hello, World!” 的程序。
演示怎么运用 ChatGPT 生成一段杂乱的代码,比方一个排序算法的完成。
介绍怎么调整 ChatGPT 的参数来优化生成的代码质量。
演示怎么优化前面演示的代码生成。
阶段 3:Q&A 互动(10 分钟)
与观众进行问答互动环节,答复关于 ChatGPT 的任何问题。
总结(5 分钟)
扼要总结 ChatGPT 的特色和运用场景。
感谢观众的收看。

是不是掌控感就强了许多?TODO也是能够用的,比方:

我会用代码注释的方法在内容上符号给你反应 ,比方”// TODO: “表明我期望你改善的点,冒号后边会跟从我要达成的效果。下面是我的输入:
ChatGPT 编程秀规划
阶段 1:介绍 ChatGPT(5 分钟)
简略介绍 ChatGPT 的根本原理和运用场景。
阶段 2:生成代码(35 分钟)// TODO: 我期望整个阶段是用一个比方串起来的,请帮我规划一个详细的比方来进行这个阶段的演示,并重新规划这个阶段的子过程
演示怎么运用 ChatGPT 生成一段简略的代码,比方一个打印 “Hello, World!” 的程序。
演示怎么运用 ChatGPT 生成一段杂乱的代码,比方一个排序算法的完成。
介绍怎么调整 ChatGPT 的参数来优化生成的代码质量。
演示怎么优化前面演示的代码生成。
阶段 3:Q&A 互动(10 分钟)
与观众进行问答互动环节,答复关于 ChatGPT 的任何问题。
总结(5 分钟)
扼要总结 ChatGPT 的特色和运用场景。
感谢观众的收看。

所以我就得到了:

ChatGPT 编程秀规划
阶段 1:介绍 ChatGPT(5 分钟)
简略介绍 ChatGPT 的根本原理和运用场景。
阶段 2:生成代码(35 分钟)
以一个比方串起来整个阶段的演示,比方:运用 ChatGPT 生成一个简略的数独求解程序。
首要演示怎么运用 ChatGPT 生成一个根本的数独求解程序,包含生成伪代码和相应的 Python 代码。
然后演示怎么运用 ChatGPT 生成一个更杂乱的数独求解程序,包含优化算法和生成相应的代码。
最终介绍怎么调整 ChatGPT 的参数来优化生成的代码质量。
留意,本阶段演示的比方不一定要是数独求解程序,能够依据需求挑选其他适合的比方。
阶段 3:Q&A 互动(10 分钟)
与观众进行问答互动环节,答复关于 ChatGPT 的任何问题。
总结(5 分钟)
扼要总结 ChatGPT 的特色和运用场景。
感谢观众的收看。

接下来我就能够依据数独游戏的比方是不是合适跟他打开对话了。这种方法是不是很强壮? 经过这种技巧我能够针对性的对AI给我的内容进行反应。

类比AI画图里,也有相似的技巧,叫做inpaint。比方众所周知,AI不拿手画手,所以呢就有人创造晰这样的技巧,比方这是个失利的手:

ChatGPT编程秀-7: 别拿编程语言不当语言

用PS涂改一下大概的手的概括以表达目的:

ChatGPT编程秀-7: 别拿编程语言不当语言

然后在AI里用符号工具把这个区域涂黑,并告知他,这个区域是手:

ChatGPT编程秀-7: 别拿编程语言不当语言

他就会依据咱们涂改的样子画一个手出来:

ChatGPT编程秀-7: 别拿编程语言不当语言

这儿Denoising strength设置的不行大,所以还不行明晰,可是能够看出这个意思。

同样的思想咱们用在编程里便是上面的那个技巧了,相似于涂改一个手的概括我还用过给他伪代码,比方在后边png_info_post_inject这个模块,生成测验框架的时分我就用过:

依据1.json中的数据结构,我需求把上面三个分支就能够简化为一个分支。

    if (given.targetFile === '1.png') {
        actualResult = await readPng(path.join(__dirname, 'cases', given.targetFile));
        assert.deepStrictEqual(actualResult, then.expectedResult);
      } else if (given.targetFile === '2.png') {
        await writePng(path.join(__dirname, 'cases', given.targetFile), 'hello', then.expectedResult);
        actualResult = await readPng(path.join(__dirname, 'cases', given.targetFile));
        assert.deepStrictEqual(actualResult, then.expectedResult);
      } else if (given.targetFile === '3.png') {
        await updatePng(path.join(__dirname, 'cases', given.targetFile), 'hello', then.expectedResult);
        actualResult = await readPng(path.join(__dirname, 'cases', given.targetFile));
        assert.deepStrictEqual(actualResult, then.expectedResult);
      }

伪代码如下:

if(given.type!=='read'){
        await png_info[given.entrypoint](given_file_path, given_params);
}
actualResult = await readPng(given_file_path);
assert.deepStrictEqual(actualResult, then.expectedResult);

这个伪代码执行的时分,第一次或许不让我满足,可是我能够依据他生成的持续批改这段伪代码,很快就得到了我想要的代码。所以说,言语便是言语,编程言语也是言语,也能够用在prompt里。

总结一下

本篇企图依照TDD思路持续写程序,但第一步就遇到生成的代码有问题的困境。

然后,咱们就创造晰一种新技巧:经过在代码中运用注释(如 // TODO: 和 // FIXME:)来指出问题所在,以便更好地与ChatGPT进行交互。这样咱们就能够针对性的对AI的内容进行反应。

这种技巧不只适用于编程范畴,还能够运用于其他范畴的文本生成。例如,在规划一个编程秀时,咱们也能够经过运用TODO和FIXME注释来对长文本进行反应,从而更好地操控输出。

接着咱们类比了AI画图中的相似技巧:inpaint,举例咱们能够经过伪代码来扮演inpaint时用概括给图片生成AI反应的方法,来给AI进行反应。

所以能够看到,别拿编程言语不当言语。很多人都在依照比较弱的产品司理跟程序员交流的方法跟AI交流,并以此幻想说未来或许不需求程序员了,但其实这种交流方法是很糟糕的,略微专业点的产品司理也会有很多手法给程序员供给更精准的反应,更何况程序员自己让AI生成代码的时分呢?

所以,当文本过长的时分,咱们要学会破门而入,直接对长文本进行反应。而不是很蠢的仅仅站在文本的外面,靠直接的描绘来企图反应的更精准,咱们要了解,言语便是言语,AI生成的内容也是言语,编程言语也是言语,乃至咱们创造的DSL也是言语,只需是言语,大言语模型的AI就能学会,就能了解。

我这一段时刻的编程秀里,或许很多人都发现,我常常会在心里吐槽,我发现一切这些心里的吐槽,其实假如直面它,都是解决方案本身。在ChatGPT年代,每逢ChatGPT给你的东西让你心里产生了什么不满,千万不要容易放过这个不满,由于你的不满或许便是改善的源泉。要直面这些不满,用自然言语把这些不满说清楚,究竟你不满的是什么。在以前,你想清楚这个,你或许无力改变实际,徒增苦楚。可是在今日,大多数情况下,一旦你想清楚了,实际都会随之改变,真实不行,你还能够跟ChatGPT商议怎样办嘛,他可比一个真实的人好脾气多了。