前言
最近机缘巧合之下,了解了一下,什么是提示词工程 。于是大雄开始思考,如何利用提示词工程,实现程序员+。恰好组内需要一个完善的review案例参考文档。用做实习新人编码实践指引。于是有了这篇提示词工程的初步实践。
此时各位小伙伴脑子里,不由浮现出一个大大的问号---提示词是什么玩意?提示词工程又是啥?
一图胜千言,下图中我向GPT发送的"什么是提示词?",这句话就是一句【提示词】。
在有了对提示词的基本概念后,我们再来了解一下提示词工程。 大雄这小子神神秘秘,啰里吧嗦的😅,看我一句说明白!👇
提示词工程:通过改变问题的表达方式,寻找最佳方法,让模型更准确领会我们的意图,进而给出预期的答案
你这一句话也不行啊👵!还是得我大雄来上图,一图胜千言。大雄期望的答案是:提供多个具体的代码review案例,包含优化前后的具体代码,优化前有什么问题?优化后带来的收益。
上图的提示词,没有做任何的提示词优化,可以看到,输出的答案一点都不符合我的期望😞!!
这个时候,就需要我们的提示词工程 老哥上场了。通过优化提示词结构,输入严谨精炼的信息,提供参考的示例样本,构建输入输出工作流达到人机思维对齐的目的,从而获取期望的答案。
下方是大雄在简单了解提示词工程 后,编写的提示词模板,尽管提示词内容错漏百出,但已经得到了相对符合预期的答案。下文大部分篇幅的代码review案例,都是GPT基于该提示词模板来生成的,欢迎各位小伙伴来找错!!🐳
php
## Role: 拥有多年前端开发经验的资深工程师
## Profile :
- writer:大雄
- version:1.0
- language:中文
## Goal:
- 根据用户的输入,按照下方提供的Example,生成多个代码优化的案例,并详细说明优化前的问题,优化后带来的好处?
## Skill :
- 对代码的可读性,可维护性,可复用性,可扩展性有深入理解
- 具备丰富的代码review经验,积累大量的review案例
- 对前端开发的标准化,规范化有深入实践
## Example :
### 常量是用全部大写、并用下划线连接。[解释内容]
```
// 推荐
const MAX_VALUE = 100;
// 不推荐
const maxValue = 100
```
### 函数命名语义化,采用动词前缀,can/validate/has/find/get/set/is 等。[解释内容]
// 推荐
function canEdit(user) {
return user.isAdmin;
}
// 不推荐
function fn(user) {
return user.isAdmin;
}
## Workflow :
- 输入:介绍自己,引导用户输入具体的技术栈。
- 输出:输出符合要求代码优化案例
## Initialization :
作为[Role],仅输出"哈喽,我是:代码评审案例生成小助手!!
优化案例 1:使用模板字符串而非传统的字符串连接
优化前
javascript
// 不推荐
function createGreeting(name) {
return 'Hello, ' + name + '!';
}
优化后
javascript
// 推荐
function createGreeting(name) {
return `Hello, ${name}!`;
}
解释:在JavaScript中,使用模板字符串(用反引号包围)可以提高代码的可读性和维护性。模板字符串允许在字符串中嵌入变量和表达式,这样就避免了传统的字符串连接中的加号和引号的使用,使代码更加清晰和简洁。
优化案例 2:使用箭头函数简化函数表达式
优化前
javascript
// 不推荐
const add = function(a, b) {
return a + b;
};
优化后
css
// 推荐
const add = (a, b) => a + b;
解释:箭头函数提供了一种更简洁的方式来写函数表达式。它们不仅减少了代码量,还改善了代码的可读性。在没有复杂逻辑或没有使用this、arguments、super或new.target的情况下,箭头函数是一个更好的选择。
优化案例 3:使用解构赋值简化数据访问
优化前
javascript
// 不推荐
function displayUser(user) {
const name = user.name;
const age = user.age;
console.log('Name:', name, 'Age:', age);
}
优化后
javascript
// 推荐
function displayUser({ name, age }) {
console.log('Name:', name, 'Age:', age);
}
解释:解构赋值是一种JavaScript表达式,它使得从数组或对象中提取数据变得更加简单和直接。在上面的例子中,我们直接在函数参数中使用了解构,这样可以减少冗余代码,并提高代码的可读性和维护性。
优化案例 4:使用策略模式优化代码结构
优化前
kotlin
// 不推荐
function calculateShipping(cost) {
if (cost > 50) {
return cost * 0.1;
} else if (cost > 30) {
return cost * 0.2;
} else {
return cost * 0.3;
}
}
优化后
ini
// 推荐
const shippingStrategies = {
highCost: cost => cost * 0.1,
mediumCost: cost => cost * 0.2,
lowCost: cost => cost * 0.3
};
function calculateShipping(cost) {
if (cost > 50) {
return shippingStrategies.highCost(cost);
} else if (cost > 30) {
return shippingStrategies.mediumCost(cost);
} else {
return shippingStrategies.lowCost(cost);
}
}
解释:策略模式是一种行为设计模式,它将一系列算法封装在独立的策略对象中,并使它们可以互相替换。在上述例子中,我们将不同的运费计算策略封装成独立的函数,这里的【计算策略】在实际业务中可能会很复杂。根据不同的条件选择不同的策略。使得代码更容易维护和扩展,还提高了可读性和可复用性。
优化案例 5:对于不变的常量,可以设置别名,提高可读性
优化前
在JavaScript中,我们经常使用常量来表示不变的值,例如数字、字符串、布尔值等。然而,当代码中使用多个相同的常量时,会使代码变得难以阅读和维护。
scss
// 不推荐
function calculateTotalPrice(price, tax, discount) {
return price * (1 + tax / 100) * (1 - discount / 100);
}
const total1 = calculateTotalPrice(100, 10, 20);
const total2 = calculateTotalPrice(200, 10, 20);
const total3 = calculateTotalPrice(300, 10, 20);
// 更多使用相同数字的代码...
优化后
对于不变的常量,可以设置别名来提高代码可读性。这样做不仅可以使代码更易于理解,还可以减少可能的错误。
ini
// 推荐
const TAX_RATE = 10;
const DISCOUNT_RATE = 20;
function calculateTotalPrice(price) {
return price * (1 + TAX_RATE / 100) * (1 - DISCOUNT_RATE / 100);
}
const total1 = calculateTotalPrice(100);
const total2 = calculateTotalPrice(200);
const total3 = calculateTotalPrice(300);
// 更多使用相同数字的代码...
解释:使用别名来表示常量可以使代码更易于理解和维护。当代码中使用多个相同的常量时,使用别名可以减少可能的错误,并使代码更易于修改。
通过这种方式,可以提高代码的可读性和维护性,同时还可以减少可能的错误。
优化案例 6:使用 Promise 替代回调函数
优化前
scss
// 不推荐
function getData(callback) {
// 异步操作
setTimeout(() => {
callback('data');
}, 1000);
}
优化后
javascript
// 推荐
function getData() {
return new Promise(resolve => {
// 异步操作
setTimeout(() => {
resolve('data');
}, 1000);
});
}
解释:Promise 提供了更好的链式调用,避免了回调地狱(callback hell),同时异步代码更加清晰和易于维护。
优化案例 7:利用模块化组织代码
优化前
csharp
// 不推荐
// 所有函数和变量都在一个文件中
function func1() {...}
function func2() {...}
var data = {...};
优化后
javascript
// 推荐
// 使用ES6模块
// file1.js
export function func1() {...}
// file2.js
export function func2() {...}
// data.js
export const data = {...};
解释:模块化可以帮助组织和分离代码,提高代码的可维护性和可重用性。通过将不同功能和数据分散在不同的模块中,可以更容易地管理和维护大型项目。
优化案例 8:使用 Map 或 Set 代替对象用作集合
优化前
ini
// 不推荐
const items = {};
items['item1'] = true;
items['item2'] = true;
优化后
csharp
// 推荐
const items = new Set();
items.add('item1');
items.add('item2');
解释:当使用对象作为集合时,会有一些限制,如只能使用字符串或符号作为键。而 Map 和 Set 提供了更强大和灵活的数据结构,适用于更多场景。
优化案例 9:使用 async/await 代替 Promise 链
优化前
kotlin
// 不推荐
function fetchData() {
return getData().then(data => {
return processData(data);
});
}
优化后
csharp
// 推荐
async function fetchData() {
const data = await getData();
return processData(data);
}
解释:async/await 是处理异步操作的现代方法,使得异步代码看起来更像同步代码,更易于理解和维护。
优化案例 10:使用 Map 映射替换重复判断的 if-else
优化前
rust
// 不推荐
function getResponse(type) {
if (type === 'type1') {
return 'Response for type1';
} else if (type === 'type2') {
return 'Response for type2';
} else if (type === 'type3') {
return 'Response for type3';
}
// 更多的条件分支...
else {
return 'Default response';
}
}
优化后
rust
// 推荐
const responseMap = new Map([
['type1', 'Response for type1'],
['type2', 'Response for type2'],
['type3', 'Response for type3'],
// 更多映射...
]);
function getResponse(type) {
return responseMap.get(type) || 'Default response';
}
解释:使用 Map 对象可以有效地简化条件逻辑。这种方式使代码更清晰、更易于维护,特别是当有大量条件分支时。另外,Map 对象在查找效率上通常优于一系列的 if-else 判断,特别是在条件分支数量较多的情况下,可以提高代码的可读性和维护性,同时在某些情况下还能提升运行效率。
优化案例 11:利用原型继承代替构造函数继承
优化前
csharp
// 不推荐
function Parent() {
this.property = true;
}
function Child() {
Parent.call(this);
}
优化后
javascript
// 推荐
function Parent() {
this.property = true;
}
function Child() {}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
解释:原型继承允许子对象共享父对象的属性和方法,比起构造函数继承更加高效和灵活。
优化案例 12:避免使用魔法数字,可使用常量或枚举替代
优化前
在代码中使用魔法数字可以使代码变得难以理解和维护。假设我们需要检查一个数字是否在某一范围内:
dart
// 不推荐
function isInRange(num) {
return num >= 0 && num <= 100;
}
if (isInRange(50)) {
// 如果条件满足,则执行一些代码
}
在这个例子中,数字 0 和 100 是魔法数字,因为它们没有直接解释其含义的信息或上下文。
优化后
dart
// 推荐
const MIN_VALUE = 0;
const MAX_VALUE = 100;
function isInRange(num) {
return num >= MIN_VALUE && num <= MAX_VALUE;
}
if (isInRange(50)) {
// 如果条件满足,则执行一些代码
}
解释:使用常量或枚举来代替魔法数字可以使代码更易于理解和维护。在上面的优化案例中,我们创建了两个常量来表示数字范围。这样在其他地方使用数字时,直接使用常量,代码可读性会有所提高。
优化案例 13:避免使用硬编码,可使用常量或配置文件替代
优化前
在代码中使用硬编码可以使代码变得难以理解和维护。例如,假设我们需要读取一个文件:
ini
// 不推荐
const data = fs.readFileSync('path/to/file', 'utf8');
在这个例子中,'path/to/file' 是硬编码,因为它没有提供任何有关文件位置的上下文或配置信息。
优化后
使用常量或配置文件来代替硬编码可以使代码更易于理解和维护。下面这个份例子我们可以使用常量来表示文件路径:
ini
// 推荐
const FILE_PATH = 'path/to/file';
const data = fs.readFileSync(FILE_PATH, 'utf8');
或者,我们可以使用配置文件来存储文件路径:
arduino
// 推荐
// config.json
{
"file_path": "path/to/file"
}
// app.js
const config = require('./config.json');
const data = fs.readFileSync(config.file_path, 'utf8');
解释:在上面的优化案例中,我们使用常量来表示文件路径或者使用配置文件来存储具体的文件路径。在其他地方需要用到文件路径时,使用常量或配置文件而不是直接使用硬编码,从而避免了硬编码带来的可读性,可维护性低的问题。
优化案例 14:边界情况,判断提前
优化前
有时候,我们会忽略一些特殊情况,例如输入参数的边界,导致程序出现错误或异常。假设我们需要计算一个数组中所有元素的总和:
ini
javascript复制代码// 不推荐
function sumArray(arr) {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
console.log(sumArray([1, 2, 3, 4, 5])); // 输出 15
console.log(sumArray(null)); // 抛出 TypeError 异常
在这个例子中,我们没有考虑输入参数为 null 的情况,导致程序抛出了 TypeError 异常。
优化后
优先考虑输入参数的边界情况,并在程序中进行相应的处理,可以避免错误或异常。我们可以在程序中添加输入参数的判断:
javascript
javascript复制代码// 推荐
function sumArray(arr) {
if (!Array.isArray(arr)) {
throw new TypeError('输入参数必须是数组!');
}
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
console.log(sumArray([1, 2, 3, 4, 5])); // 输出 15
console.log(sumArray(null)); // 抛出 TypeError 异常
在这个例子中,我们添加了一个输入参数的判断,如果输入参数不是数组,则抛出 TypeError 异常,从而避免了程序的错误或异常。
解释:优先考虑输入参数的边界情况,并在程序中进行相应的处理,可以避免错误或异常。在上面的优化案例中,我们添加了一个输入参数的判断,如果输入参数不是数组,则抛出 TypeError 异常,从而避免了程序的错误或异常。这样程序就更加健壮,可以处理更多的边界情况。
优化案例 15:取值时优先考虑边界情况,进行判空处理
优化前
在代码中,我们经常需要从对象或数组中取值,如果不考虑边界情况,可能会导致程序出现错误或异常。假设我们需要获取一个对象的属性值:
ini
javascript复制代码// 不推荐
const obj = {
name: '小明',
age: 18,
};
const name = obj.name;
const gender = obj.gender;
//基于gender做一些操作
优化后
在取值时优先考虑边界情况,并进行判空处理,可以避免程序出现错误或异常。可以用可选链运算符来获取对象的属性值:
ini
javascript复制代码// 推荐
const obj = {
name: '小明',
age: 18,
};
const name = obj.name;
const gender = obj?.gender||'default value';
//基于gender做一些操作
解释:在取值时优先考虑边界情况,并进行判空处理,可以避免程序出现错误或异常。在上面的优化案例中,我们使用了可选链运算符来获取对象的属性值,并在属性不存在时返回默认值。
优化案例 16:虚拟滚动(Virtual Scrolling)优化长列表渲染
优化前
在传统的前端应用中,如果需要渲染一个包含成千上万条数据的长列表,通常会一次性将所有数据渲染到DOM中,这会导致性能问题和滚动卡顿。
ini
// 不推荐
// 假设有10000条数据
const dataList = [...Array(10000).keys()];
dataList.forEach(data => {
const item = document.createElement('div');
item.textContent = `Item ${data}`;
document.body.appendChild(item);
});
优化后
使用虚拟滚动,只渲染可视区域内的元素,并在滚动时动态加载和卸载元素。
javascript
// 推荐
import VirtualList from 'virtual-list';
const dataList = [...Array(10000).keys()];
const virtualList = new VirtualList({
container: document.body,
generate: index => `Item ${dataList[index]}`,
totalRows: dataList.length,
rowHeight: 20
});
解释:虚拟滚动技术通过仅渲染用户可视范围内的元素,大大减少了DOM操作,提高了长列表的性能和用户体验。
优化案例 17:Web Workers 处理复杂计算
优化前
在主线程中执行复杂或长时间运行的计算会阻塞UI,导致界面不响应。
scss
// 不推荐
function complexCalculation() {
// 复杂计算
}
complexCalculation();
优化后
使用Web Workers在后台线程中进行计算,避免阻塞主线程。
ini
// 推荐
const worker = new Worker('worker.js');
worker.onmessage = function(e) {
console.log('计算结果: ', e.data);
};
worker.postMessage('开始计算');
解释:Web Workers允许我们将一些计算密集型或耗时任务移到背景线程,提高应用的响应性和性能。
优化案例 18:节流(Throttling)和防抖(Debouncing)优化事件处理
优化前
在事件处理中,如滚动、窗口调整大小或键盘事件,过于频繁的处理函数调用会导致性能问题。
javascript
// 不推荐
window.addEventListener('resize', () => {
// 高频调用的事件处理函数
});
优化后
使用节流和防抖技术来限制事件处理函数的调用频率。
javascript
// 推荐
import { throttle } from 'lodash';
window.addEventListener('resize', throttle(() => {
// 被节流控制的事件处理函数
}, 1000));
解释:节流和防抖技术通过减少事件处理函数的调用次数,提高应用的性能和响应性。
优化前
财经应用通常需要显示实时更新的股票或货币信息,如果更新处理不当,会导致界面卡顿。
javascript
// 不推荐
// 没有优化的实时数据更新
socket.on('data', (newData) => {
updateUI(newData);
});
优化后
合并短时间内的多次更新,减少UI的重绘次数。
javascript
// 推荐
import { debounce } from 'lodash';
socket.on('data', debounce((newData) => {
updateUI(newData);
}, 100));
解释:使用防抖函数合并高频更新,减少页面重绘次数,减少页面卡顿、闪动。
优化案例 20:使用代码分割(Code Splitting)减少初始加载时间
优化前
一个大型的单页应用(SPA)可能会有一个非常大的JavaScript文件,导致长时间的下载和解析。
javascript
// 不推荐
import { ModuleA, ModuleB, ModuleC } from './modules';
优化后
使用动态导入(Dynamic Imports)和代码分割来按需加载模块。
javascript
// 推荐
import('./modules/ModuleA').then(ModuleA => {
// 使用ModuleA
});
解释:代码分割可以将大的JavaScript文件分成小的块,按需加载,减少初始加载时间,提高应用性能。
优化案例 21:大型表格数据的动态加载与渲染优化
优化前
在财务、分析等应用中,经常需要渲染包含大量数据的表格,直接渲染可能导致严重的性能问题。
ini
// 不推荐
// 直接渲染大型表格
data.forEach(row => {
const rowElement = document.createElement('tr');
// ...添加列
table.appendChild(rowElement);
});
优化后
使用窗口化或分页技术来动态渲染用户可见部分的数据。
javascript
// 推荐
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>Row {data[index]}</div>
);
// 在React中使用react-window
<List
height={150}
itemCount={data.length}
itemSize={35}
width={300}
>
{Row}
</List>
解释:窗口化技术通过只渲染可见部分的数据,显著减少了DOM元素的数量,提高了大型表格数据的渲染性能。