0%

一个前端研发视角下的ChatGPT使用体验

一个前端研发视角下的ChatGPT使用体验

简介

本文内容由VsCode-ChatGPT辅助生成。

ChatGPT的发布引来了一轮新的浪潮,对各个行业均有所冲击。互联网上目前已存在大量对话案例,但普遍深度不够,且场景不够聚焦。

本文主要从以下几个方面,从微观到宏观的将一些日常开发者所面对的工作交由ChatGPT来处理以期洞察ChatGPT在哪些方面能提升研发体验,为研发提效。

  1. 代码注释/解释:给业务代码添加注释、解释开源项目代码、解释复杂算法、解释开源项目方案。
  2. 代码生成:根据研发者提出的细节需求生成一些符合预期的业务代码或者代码片段。
  3. 方案生成:根据研发者提出的宏观需求做技术调研并生成方案。
  4. 代替面试:代替人工面试。
  5. 开放性问题:应对一些日常工作生活中的开放性问题。

细分领域预警,长篇幅预警,本文专注于前端研发领域,且使用了较多案例,包含大量代码,针对特定案例可先看小结再基于兴趣决定是否详细查看分析过程。

考虑到篇幅因素,注册及安装教程不再赘述。

代码解释

本节尝试测试ChatGPT对于代码解释的能力,大致可以分为几个部分:

  1. 辅助开发者进行代码注释
  2. 辅助开发者阅读开源项目
  3. 辅助开发者理解复杂算法
  4. 辅助开发者理解复杂工程

辅助开发者进行代码注释

在该案例中将引入笔者日常开发中遇到的一段业务代码。尝试使用ChatGPT对代码进行注释和解释。

原始代码

一段针对Js对象做特殊编码的代码

这里给出了一段业务代码、意图为编码一个名为chartsOption对象,一是编码为Json格式另一个为编码为Zip格式。其中以Zip方式编码的会同时将chartsOption对应的截图压入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import JSZip from "jszip";
export const enCodeToJson = function (chartsOption) {
return new Blob([JSON.stringify(chartsOption, undefined, 2)], {
type: "text/plain;charset=utf-8",
});
};
export const enCodeToZip = async function (chartsOption, itemScreenShot) {
const xgCharts = new JSZip();
for (let i = 0; i < chartsOption.length; i++) {
chartsOption[i].screenShotUrl = `./${chartsOption[i].uuid}.svg`;
}
for (let i = 0; i < itemScreenShot.length; i++) {
xgCharts.file(
itemScreenShot[i].name,
decodeURIComponent(itemScreenShot[i].svg.split(",")[1])
);
}
xgCharts.file(
"chartPackage.json",
new Blob([JSON.stringify(chartsOption, undefined, 2)], {
type: "text/plain;charset=utf-8",
})
);
for (let i = 0; i < itemScreenShot.length; i++) {
xgCharts.file(
itemScreenShot[i].name,
decodeURIComponent(itemScreenShot[i].svg.split(",")[1])
);
}
const xgChartsContent = await xgCharts.generateAsync({ type: "blob" });
return xgChartsContent;
};

人工注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import JSZip from "jszip";

/**
* 以Json的编码方式编码XGCharts
*
* @param {object} chartsOption 图表配置项
*/
export const enCodeToJson = function (chartsOption) {
return new Blob([JSON.stringify(chartsOption, undefined, 2)], {
type: "text/plain;charset=utf-8",
});
};
/**
*
* 以zip的编码方式编码XGCharts
*
* @param {object} chartsOption 图表配置项
* @param {array} itemScreenShot 各个图表的截图
* @return {object}
*/
export const enCodeToZip = async function (chartsOption, itemScreenShot) {
const xgCharts = new JSZip();
// 将截屏的url信息写入 图表的配置项
for (let i = 0; i < chartsOption.length; i++) {
chartsOption[i].screenShotUrl = `./${chartsOption[i].uuid}.svg`;
}
// 压缩 截图
for (let i = 0; i < itemScreenShot.length; i++) {
xgCharts.file(
itemScreenShot[i].name,
decodeURIComponent(itemScreenShot[i].svg.split(",")[1])
);
}
// 压缩 option
xgCharts.file(
"chartPackage.json",
new Blob([JSON.stringify(chartsOption, undefined, 2)], {
type: "text/plain;charset=utf-8",
})
);

// 压缩 截图
for (let i = 0; i < itemScreenShot.length; i++) {
xgCharts.file(
itemScreenShot[i].name,
decodeURIComponent(itemScreenShot[i].svg.split(",")[1])
);
}
const xgChartsContent = await xgCharts.generateAsync({ type: "blob" });
return xgChartsContent;
};

ChatGPT解释

上面的代码使用JSZip库将内容压缩并打包到单个zip文件中。它首先导入JSZip库,然后创建两个函数,enCodeToJson和enCodeToZip。
enCodeToJson函数接受一个参数chartsOption,并生成一个新的Blob对象,其内容是chartsOption参数的字符串化版本,字符集设置为UTF-8。
enCodeToZip函数接受chartsOption和itemScreenShot,并概括一个新的JSZip对象。然后,它遍历chartsOption中的每个项目,并为每个项目设置screenShotUrl。然后,它将enCodeToJson函数创建的chartPackage.json放入JSZip对象。最后,它生成一个包含压缩内容的Blob对象。

ChatGPT整体注释

代码首先导入库“JSZip”,该库允许JavaScript代码读取和创建zip文件。
下一个代码块是一个导出函数,负责通过将图表转换为json格式来处理图表。
最后一个代码块是一个异步导出函数,它处理一个项目的屏幕截图,并将其添加到一个带有关键字“chartPackage.json”的zip文件中。最后,它返回生成的zip文件的内容。

ChatGPT按行注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import JSZip from "jszip";

// enCodeToJson函数用于将chartsOption编码为JSON并返回blob
export const enCodeToJson = function (chartsOption) {
return new Blob([JSON.stringify(chartsOption, undefined, 2)], {
type: "text/plain;charset=utf-8",
});
};

export const enCodeToZip = async function (chartsOption, itemScreenShot) {
const xgCharts = new JSZip();
// 此代码将chartsOption数组中每个项的screenShotUrl属性设置为/${chartsOption[i].uuid}.svg。这允许chartsOptions数组的每个元素都有自己的屏幕截图URL。
for (let i = 0; i < chartsOption.length; i++) {
chartsOption[i].screenShotUrl = `./${chartsOption[i].uuid}.svg`;
}

// 该代码在itemScreenShot数组中循环,并为数组中的每个项目创建一个文件,文件名作为对象名,文件内容是从项目的svg属性解码的svg数据。然后将svg数据分成两部分,只使用第二部分。
for (let i = 0; i < itemScreenShot.length; i++) {
xgCharts.file(
itemScreenShot[i].name,
decodeURIComponent(itemScreenShot[i].svg.split(",")[1])
);
}

// 这段代码创建了一个名为“chartPackage.json”的新文件,其中包含chartsOption参数的json字符串版本,并为缩进添加了两个空格。该文件被指定为MIME类型(text/plain;charset=utf-8),以指定浏览器应该如何读取和解释该文件。
xgCharts.file(
"chartPackage.json",
new Blob([JSON.stringify(chartsOption, undefined, 2)], {
type: "text/plain;charset=utf-8",
})
);

for (let i = 0; i < itemScreenShot.length; i++) {
xgCharts.file(
itemScreenShot[i].name,
decodeURIComponent(itemScreenShot[i].svg.split(",")[1])
);
}
// 此代码导入JSZip库。enCodeToJson函数接受chartsOption参数,将其字符串化并返回一个blob对象。enCodeToZip函数用于压缩文件和可能包含的任何其他屏幕截图。xgCharts变量被分配了一个新的JSZip实例,并且为chartOptions数组中的每个元素设置了screenShotUrl字段。然后将与项目截图相关联的文件添加到xgCharts实例中。blob形式的json对象也添加到xgCharts文件中。xgChartsContent变量将生成的zip文件作为blob进行签名。
const xgChartsContent = await xgCharts.generateAsync({ type: "blob" });
return xgChartsContent;
};

ChatGPT BUG发现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 如果不知道用户打算如何处理代码,很难判断代码是否存在任何问题。编写的代码可能无法达到预期效果。
import JSZip from "jszip";
export const enCodeToJson = function (chartsOption) {
return new Blob([JSON.stringify(chartsOption, undefined, 2)], {
type: "text/plain;charset=utf-8",
});
};
export const enCodeToZip = async function (chartsOption, itemScreenShot) {
const xgCharts = new JSZip();
for (let i = 0; i < chartsOption.length; i++) {
chartsOption[i].screenShotUrl = `./${chartsOption[i].uuid}.svg`;
}
for (let i = 0; i < itemScreenShot.length; i++) {
xgCharts.file(
itemScreenShot[i].name,
decodeURIComponent(itemScreenShot[i].svg.split(",")[1])
);
}
xgCharts.file(
"chartPackage.json",
new Blob([JSON.stringify(chartsOption, undefined, 2)], {
type: "text/plain;charset=utf-8",
})
);
for (let i = 0; i < itemScreenShot.length; i++) {
xgCharts.file(
itemScreenShot[i].name,
decodeURIComponent(itemScreenShot[i].svg.split(",")[1])
);
}
const xgChartsContent = await xgCharts.generateAsync({ type: "blob" });
return xgChartsContent;
};

小结

在该案例中ChartGPT对于代码的注释和解释已经足够专业,笔者认为在有人工Review的情景下已经可以直接应用于生产,辅助生成注释,以后很难再有**程序员最讨厌的事情是啥?写注释!最最讨厌的事情是啥?别人不写注释!**的梗。

一些细节:

  1. 相较于 Explain the following code(解释如下代码) 似乎 Add comments for the following code(给如下代码添加注释更加准确)
  2. 针对于方法级的注释足够简要和准确,针对代码块级的注释足够充分。
  3. find bugs(发现BUG) 由于缺乏对代码预期的理解,似乎并不能正常工作(在上述案例中并没有发现有重复执行截屏压缩的问题)

辅助开发者阅读开源项目

在该案例中将引入一段ECharts的代码,niceExtentChartGPT去解释具体原理,以降低开发者对于开源项目的理解成本。

原始代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
niceExtent(opt: {
splitNumber: number, // By default 5.
fixMin?: boolean,
fixMax?: boolean,
minInterval?: number,
maxInterval?: number
}): void {
const extent = this._extent;
// If extent start and end are same, expand them
if (extent[0] === extent[1]) {
if (extent[0] !== 0) {
// Expand extent
const expandSize = extent[0];
// In the fowllowing case
// Axis has been fixed max 100
// Plus data are all 100 and axis extent are [100, 100].
// Extend to the both side will cause expanded max is larger than fixed max.
// So only expand to the smaller side.
if (!opt.fixMax) {
extent[1] += expandSize / 2;
extent[0] -= expandSize / 2;
}
else {
extent[0] -= expandSize / 2;
}
}
else {
extent[1] = 1;
}
}
const span = extent[1] - extent[0];
// If there are no data and extent are [Infinity, -Infinity]
if (!isFinite(span)) {
extent[0] = 0;
extent[1] = 1;
}

this.niceTicks(opt.splitNumber, opt.minInterval, opt.maxInterval);

// let extent = this._extent;
const interval = this._interval;

if (!opt.fixMin) {
extent[0] = roundNumber(Math.floor(extent[0] / interval) * interval);
}
if (!opt.fixMax) {
extent[1] = roundNumber(Math.ceil(extent[1] / interval) * interval);
}
}

人工注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 这部分将真实的数值范围变成了美化后的以整数分割的范围
niceExtent(opt: {
splitNumber: number, // 轴线的分段数 默认是5
fixMin?: boolean, // 是否需要将最小值`nice`化
fixMax?: boolean, // 是否需要将最大值`nice`化
minInterval?: number, // 最小间隔 默认为undefined
maxInterval?: number // 最大间隔 默认为undefined
}): void {
const extent = this._extent;
// 如果范围的开始和结束一样 处理逻辑
if (extent[0] === extent[1]) {
if (extent[0] !== 0) {
const expandSize = extent[0];
//在以下案例中
// 轴已固定最大值100
// 加上数据均为100,轴范围为[100,100]。
// 向两侧延伸将导致扩展最大值大于固定最大值。
// 所以只扩大到较小的一侧。
if (!opt.fixMax) {
extent[1] += expandSize / 2;
extent[0] -= expandSize / 2;
}
else {
extent[0] -= expandSize / 2;
}
}
else {
extent[1] = 1;
}
}
const span = extent[1] - extent[0];
// 如果范围是默认的无限的处理逻辑
if (!isFinite(span)) {
extent[0] = 0;
extent[1] = 1;
}

// 核心代码 先 将【分段】nice 化,再将【范围】nice 化
this.niceTicks(opt.splitNumber, opt.minInterval, opt.maxInterval);

// let extent = this._extent;
const interval = this._interval;

if (!opt.fixMin) {
extent[0] = roundNumber(Math.floor(extent[0] / interval) * interval);
}
if (!opt.fixMax) {
extent[1] = roundNumber(Math.ceil(extent[1] / interval) * interval);
}
}

ChatGPT解释

此函数将当前轴的最大值和最小值放入opt.splitNumber、opt.minInterval、opt.maxInterval参数,并调用niceTicks()函数。之后,它会将生成的interval绑定到this中,并根据opt.fixMin和opt.fixMax来确定最终的最小值和最大值,并将它们舍入到roundNumber()函数所返回的合理间隔。

ChatGPT整体注释

此函数的功能是调用niceTicks()将this._extent的开头和结尾元素按照opt.splitNumber,opt.minInterval,opt.maxInterval 参数内容进行修整。其中fixMin和fixMax参数用于设置是否将开头和结尾元素进行修整。

小结

在该案例中,由于大型开源项目普遍具有较高的封装度、ChatGPT无法一次性的去解释整个业务逻辑。仍需要开发者根据代码引用关系不断定位具体代码。但在方法级的理解上ChatGPT仍然表现良好,可以给开发者提供一定的帮助,以降低理解成本和更方便的聚焦核心代码。

ChatGPT可以起到降低一定理解成本的作用,但深入理解具体业务逻辑或工作原理仍需要一定的心智负担。

一些细节:

  1. 这里再次验证了相较于解释ChatGPT似乎更擅长注释
  2. ChatGPT并不擅长基于变量名来推断所对应的具体含义。

以下是将ChatGPT的整体注释加入了基于变量名的人工推断后的解释:

此函数的功能是调用niceTicks()将【输入的范围】的开头和结尾元素按照【配置项中的:所需要分割的段数、最小间隔、最大间隔】 参数内容进行修整。其中【修改最小值】和【修改最大值】参数用于设置是否将开头和结尾元素进行修整。

辅助开发者理解复杂算法

该案例中将引入一段D3的代码, TreeMap矩形树图布局算法,让ChartGPT去解释具体原理和执行逻辑,以降低开发者对于复杂算法的理解成本。

原始代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
  function treemap(root) {
root.x0 =
root.y0 = 0;
root.x1 = dx;
root.y1 = dy;
root.eachBefore(positionNode);
paddingStack = [0];
if (round) root.eachBefore(roundNode);
return root;
}

function positionNode(node) {
var p = paddingStack[node.depth],
x0 = node.x0 + p,
y0 = node.y0 + p,
x1 = node.x1 - p,
y1 = node.y1 - p;
if (x1 < x0) x0 = x1 = (x0 + x1) / 2;
if (y1 < y0) y0 = y1 = (y0 + y1) / 2;
node.x0 = x0;
node.y0 = y0;
node.x1 = x1;
node.y1 = y1;
if (node.children) {
p = paddingStack[node.depth + 1] = paddingInner(node) / 2;
x0 += paddingLeft(node) - p;
y0 += paddingTop(node) - p;
x1 -= paddingRight(node) - p;
y1 -= paddingBottom(node) - p;
if (x1 < x0) x0 = x1 = (x0 + x1) / 2;
if (y1 < y0) y0 = y1 = (y0 + y1) / 2;
tile(node, x0, y0, x1, y1);
}
}

function tile(parent, x0, y0, x1, y1,ratio = (1 + Math.sqrt(5)) / 2) {
var rows = [],
nodes = parent.children,
row,
nodeValue,
i0 = 0,
i1 = 0,
n = nodes.length,
dx, dy,
value = parent.value,
sumValue,
minValue,
maxValue,
newRatio,
minRatio,
alpha,
beta;
while (i0 < n) {
dx = x1 - x0, dy = y1 - y0;

do sumValue = nodes[i1++].value; while (!sumValue && i1 < n);
minValue = maxValue = sumValue;
alpha = Math.max(dy / dx, dx / dy) / (value * ratio);
beta = sumValue * sumValue * alpha;
minRatio = Math.max(maxValue / beta, beta / minValue);

for (; i1 < n; ++i1) {
sumValue += nodeValue = nodes[i1].value;
if (nodeValue < minValue) minValue = nodeValue;
if (nodeValue > maxValue) maxValue = nodeValue;
beta = sumValue * sumValue * alpha;
newRatio = Math.max(maxValue / beta, beta / minValue);
if (newRatio > minRatio) { sumValue -= nodeValue; break; }
minRatio = newRatio;
}

rows.push(row = {value: sumValue, dice: dx < dy, children: nodes.slice(i0, i1)});

if (row.dice) treemapDice(row, x0, y0, x1, value ? y0 += dy * sumValue / value : y1);
else treemapSlice(row, x0, y0, value ? x0 += dx * sumValue / value : x1, y1);
value -= sumValue, i0 = i1;
}
return rows;
}

function treemapSlice(parent, x0, y0, x1, y1) {
var nodes = parent.children,
node,
i = -1,
n = nodes.length,
k = parent.value && (y1 - y0) / parent.value;

while (++i < n) {
node = nodes[i], node.x0 = x0, node.x1 = x1;
node.y0 = y0, node.y1 = y0 += node.value * k;
}
}

function treemapDice(parent, x0, y0, x1, y1) {
var nodes = parent.children,
node,
i = -1,
n = nodes.length,
k = parent.value && (x1 - x0) / parent.value;

while (++i < n) {
node = nodes[i], node.y0 = y0, node.y1 = y1;
node.x0 = x0, node.x1 = x0 += node.value * k;
}
}

ChatGPT整体解释

提供的代码是树图算法,用于使用嵌套矩形显示分层数据。 它首先遍历根节点并设置 x0、y0、x1 和 y1 的值(树图需要填充空间的坐标)。
然后它进入一个循环,为每个节点调用 positionNode 函数。 此函数设置节点的边界,定义它们相对于父节点的大小和位置。 paddingStack 数组确定应用于树图中每个节点的填充。
在此之后,调用 tile 函数。 此函数将空间划分为可用于绘制树状图的矩形网格。 它首先计算定义为 (1 + 5 的平方根) 除以 2 的比率,该比率可用于查找给定区域中矩形的最大数量,同时得出树状图的最佳视觉表示。 之后,它循环遍历与父节点关联的节点列表,以确定每个节点的值,例如最小值和最大值、所有值的总和以及每个节点的实际分块大小。
接下来,调用 treemapSlice 和 treemapDice 函数。 这两个函数与 tile 函数一起负责计算和布置树图中的各个图块。 treemapSlice 函数从上到下划分瓦片,而 treemapDice 函数从左到右划分瓦片。

人工解释

这里以顶层为例,给出了对一层层级数据的布局逻辑。每深入一层即更新当前节点的,画布大小(size) 重复执行该逻辑。

  1. 根据给出的画布区大小(size)确定长宽比,如果长度大于宽度优先进行行布局,如果宽度大于长度优先进行列布局。即按照沿最短边优先开始。如果进行行布局即已经确定该行的宽度即为画布区的宽度,如果进行列布局即已经确定该列的高度即为画布区的高度。
  2. 根据长宽比确定一个单元(行或列)的最大范围。如果是行即确定当前行的行高,如果是列即确定该列的列宽。
  3. 向该单元压入数据,如果是行布局即该单元的宽度已经确定,如果是进行列布局当前列的高度已经确定。每压入一次数据就可以计算一次该单元的长宽比如果超出给出的长宽比即停止向当前单元压入数据。
  4. 确定该单元的子节点的和,作为当前单元的值。根据子节点的值和当前单元的值对子节点进行相应的布局
  5. 由于步骤2中创建了一个单元占用了一定的空间。重新确定当前空白区域的开始坐标。更新画布区的坐标。
  6. 重复步骤1

人工注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
 function treemap(root) {
// 根据 给出的size信息 将可视化信息写入矩阵树(Treemap)的根节点
root.x0 =
root.y0 = 0;
// 根节点的宽度为整个图表的宽度
root.x1 = dx;
// 根节点的高度为整个图表的高度
root.y1 = dy;

// 这里对节点做了一个层次遍历
root.eachBefore(positionNode);
paddingStack = [0];
// 如果必要的话 会对所有的节点坐标做一个 四舍五入的处理
if (round) root.eachBefore(roundNode);
return root;
}

// 层次遍历的回调函数 对节点进行布局
function positionNode(node) {
// 这里对每个区块的padding做了处理
var p = paddingStack[node.depth],
x0 = node.x0 + p,
y0 = node.y0 + p,
x1 = node.x1 - p,
y1 = node.y1 - p;
if (x1 < x0) x0 = x1 = (x0 + x1) / 2;
if (y1 < y0) y0 = y1 = (y0 + y1) / 2;
node.x0 = x0;
node.y0 = y0;
node.x1 = x1;
node.y1 = y1;
// 这对开始对当前节点的子节点进行布局
if (node.children) {
// 还是对节点的padding的处理
p = paddingStack[node.depth + 1] = paddingInner(node) / 2;
x0 += paddingLeft(node) - p;
y0 += paddingTop(node) - p;
x1 -= paddingRight(node) - p;
y1 -= paddingBottom(node) - p;
if (x1 < x0) x0 = x1 = (x0 + x1) / 2;
if (y1 < y0) y0 = y1 = (y0 + y1) / 2;
// 传入父节点的布局信息对子节点进行布局
tile(node, x0, y0, x1, y1);
}
}
// 以d3默认的treemapSquarify算法为例,其他的还包括treemapBinary、treemapDice、treemapSlice、treemapSliceDice、treemapResquarify。
function tile(parent, x0, y0, x1, y1,ratio = (1 + Math.sqrt(5)) / 2) {
var rows = [],
nodes = parent.children, //当前节点的子节点列表
row,
nodeValue,
i0 = 0,
i1 = 0,
n = nodes.length,
dx, dy, // 当前节点的 高度差 与 宽度差
value = parent.value, // 父节点的值大小
sumValue, // 当前行或列的总值的大小
minValue,
maxValue,
newRatio,
minRatio,
alpha,
beta;
// 根据当前节点的信息 对 当前节点的子节点进行布局
while (i0 < n) {
// 当前节点的 高度差 与 宽度差
dx = x1 - x0, dy = y1 - y0;

// 找到下一个非空的值
do sumValue = nodes[i1++].value; while (!sumValue && i1 < n);
minValue = maxValue = sumValue;
alpha = Math.max(dy / dx, dx / dy) / (value * ratio);
beta = sumValue * sumValue * alpha;
minRatio = Math.max(maxValue / beta, beta / minValue);

// 在纵横比保持或提高时继续添加节点。
for (; i1 < n; ++i1) {
// 这里持续对当前单元压入数据
sumValue += nodeValue = nodes[i1].value;
if (nodeValue < minValue) minValue = nodeValue;
if (nodeValue > maxValue) maxValue = nodeValue;
beta = sumValue * sumValue * alpha;
newRatio = Math.max(maxValue / beta, beta / minValue);
// 判断当前单元超出给出的长宽比时停止压入数据。
if (newRatio > minRatio) { sumValue -= nodeValue; break; }
minRatio = newRatio;
}

// 定位并记录行方向。 dice来记录方向 按照沿最短边优先开始
// 这里拿到了 一行 或者一列 数据 i0 表示数据的起始下标 i1表示结束
rows.push(row = {value: sumValue, dice: dx < dy, children: nodes.slice(i0, i1)});

// 这里根据 方向 对当前节点的坐标做了计算
// x1 和 y1 即 当前节点的终止点不会变
// x0 和 y0 即 当前节点子节点的开始坐标在发生变化
if (row.dice) treemapDice(row, x0, y0, x1, value ? y0 += dy * sumValue / value : y1);
else treemapSlice(row, x0, y0, value ? x0 += dx * sumValue / value : x1, y1);
value -= sumValue, i0 = i1;
}
return rows;
}

// 对一行数据进行行布局 进行行布局时确定行的高度
// 传入的数据为当前行的节点信息parent 以及当前行的开始结束坐标
// 由于是 沿最短边优先开始 每次布局一行或一列 所以结束坐标不会变化 变化的仅为开始坐标
function treemapSlice(parent, x0, y0, x1, y1) {
var nodes = parent.children,
node,
i = -1,
n = nodes.length,
// 拿到了值为1是占当前行的比例
k = parent.value && (y1 - y0) / parent.value;

while (++i < n) {
node = nodes[i], node.x0 = x0, node.x1 = x1;
// 根据比例 和 当前节点的值 进行横向布局
node.y0 = y0, node.y1 = y0 += node.value * k;
}
}

// 对一列数据进行列布局 进行行布局时确定列的宽度
function treemapDice(parent, x0, y0, x1, y1) {
var nodes = parent.children,
node,
i = -1,
n = nodes.length,
// 拿到了值为1是占当前列的比例
k = parent.value && (x1 - x0) / parent.value;

while (++i < n) {
node = nodes[i], node.y0 = y0, node.y1 = y1;
// 根据比例 和 当前节点的值 进行纵向布局
node.x0 = x0, node.x1 = x0 += node.value * k;
}
}

ChatGPT注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
 // 初始化树映射值的函数
function treemap(root) {
// 将根x,y位置设置为0
root.x0 =
root.y0 = 0;
// 将根x,y限制设置为dx,dy
root.x1 = dx;
root.y1 = dy;
// 将初始填充堆栈设置为[0]
root.eachBefore(positionNode);
paddingStack = [0];

//如果需要,将节点舍入
if (round) root.eachBefore(roundNode);
return root;
}

function positionNode(node) {
// 设置首次padding值
var p = paddingStack[node.depth],
// 获取节点左上顶点坐标
x0 = node.x0 + p,
y0 = node.y0 + p,
// 获取节点右下顶点坐标
x1 = node.x1 - p,
y1 = node.y1 - p;
// 判断节点右下顶点是否在左上顶点的右侧,若不在则重新计算左上顶点与右下顶点的中心位置
if (x1 < x0) x0 = x1 = (x0 + x1) / 2;
if (y1 < y0) y0 = y1 = (y0 + y1) / 2;

// 更新节点左上顶点与右下顶点的坐标位置
node.x0 = x0;
node.y0 = y0;
node.x1 = x1;
node.y1 = y1;
// 判断节点是否包含子节点,若有则进行递归调用
if (node.children) {
// 获取当前node节点的深度下padding值
p = paddingStack[node.depth + 1] = paddingInner(node) / 2;
// 重新计算节点左上顶点坐标
x0 += paddingLeft(node) - p;
y0 += paddingTop(node) - p;

// 重新计算节点右下顶点坐标
x1 -= paddingRight(node) - p;
y1 -= paddingBottom(node) - p;

// 判断右下顶点是否在左上顶点的右侧,若不在则重新计算左上顶点与右下顶点的中心位置
if (x1 < x0) x0 = x1 = (x0 + x1) / 2;
if (y1 < y0) y0 = y1 = (y0 + y1) / 2;

// 递归调用tile函数用于计算该节点下所有节点的左上顶点与右下顶点的坐标位置
tile(node, x0, y0, x1, y1);
}
}

function tile(parent, x0, y0, x1, y1,ratio = (1 + Math.sqrt(5)) / 2) {
var rows = [], //用于存储每行平铺的数组
nodes = parent.children, //给定父节点内的节点数组
row, // 单行瓦片
nodeValue, // 每个节点的值
i0 = 0, // 循环的起始索引
i1 = 0, // 循环的结束索引
n = nodes.length, // 节点总数
dx, dy, // 存储当前图块尺寸的变量
value = parent.value, // 所有节点值的总和
sumValue, // 当前瓦片行中值的总和
minValue, // 给定节点集的最小值
maxValue, // 给定节点集的最大值
newRatio, // 用于确定平铺中使用的比率的变量
minRatio, // 当前循环中的最小比率
alpha, // 用于计算newRatio
beta; // 用于计算newRatio

// 遍历所有节点
while (i0 < n) {
// 获取当前瓦片的dx和dy
dx = x1 - x0, dy = y1 - y0;

// 添加节点的值
do sumValue = nodes[i1++].value; while (!sumValue && i1 < n);

// 将 minValue 和 maxValue 设置为它们的初始值
minValue = maxValue = sumValue;

// 计算比率计算中要使用的 alpha 和 beta
alpha = Math.max(dy / dx, dx / dy) / (value * ratio);
beta = sumValue * sumValue * alpha;

// 计算最小比率
minRatio = Math.max(maxValue / beta, beta / minValue);

// 循环添加值并检查比率
for (; i1 < n; ++i1) {
sumValue += nodeValue = nodes[i1].value;
if (nodeValue < minValue) minValue = nodeValue;
if (nodeValue > maxValue) maxValue = nodeValue;
beta = sumValue * sumValue * alpha;
newRatio = Math.max(maxValue / beta, beta / minValue);
if (newRatio > minRatio) { sumValue -= nodeValue; break; }
minRatio = newRatio;
}

// 将瓦片行推入行数组
rows.push(row = {value: sumValue, dice: dx < dy, children: nodes.slice(i0, i1)});

// 确定该行将使用哪种平铺方法
if (row.dice) treemapDice(row, x0, y0, x1, value ? y0 += dy * sumValue / value : y1);
else treemapSlice(row, x0, y0, value ? x0 += dx * sumValue / value : x1, y1);
value -= sumValue, i0 = i1;
}
return rows;
}

// treemapSlice 函数
// parent:树状图的父元素
// x0, y0, x1, y1:树状图的边界坐标
function treemapSlice(parent, x0, y0, x1, y1) {
//nodes:父节点的子节点列表
var nodes = parent.children,
node, // 代表当前节点
i = -1, // 节点索引
n = nodes.length, // 子节点数
k = parent.value && (y1 - y0) / parent.value; // y1 和 y2 之间的差值除以总父值

// 为每个节点计算单独的切片
while (++i < n) {
node = nodes[i],
// 从树图的边界设置x0和x1
node.x0 = x0, node.x1 = x1;
// 从树图的边界设置 y0 和 y1
node.y0 = y0, node.y1 = y0 += node.value * k;
}
}


// 这个函数递归地根据值划分树图
// 将其子项放入由参数定义的矩形中
// x0、y0、x1 和 y1,同时独立调整参数。
function treemapDice(parent, x0, y0, x1, y1) {
// 分配的节点是父节点的子节点
var nodes = parent.children,
node,
i = -1,
n = nodes.length,
// k 是宽度/高度乘以值的比率
k = parent.value && (x1 - x0) / parent.value;

// 遍历每个子节点
while (++i < n) {
node = nodes[i],
// 将上面提供的参数分配给节点
node.y0 = y0, node.y1 = y1;
node.x0 = x0, node.x1 = x0 += node.value * k;
}
}

小结

在该案例中,ChatGPT表现出了对文件级代码,以及复杂算法的可解释性,根据代码的执行顺序对算法的执行逻辑做了整体阐述。但相较于人工的更关注宏观的执行逻辑,ChatGPT更倾向于表达算法的一些内部细节。

对于算法的理解ChatGPT可以起到一定的启发作用,平滑理解复杂算法的陡峭曲线。但仍需一定的精力投入去理解宏观逻辑。

辅助开发者理解复杂工程

Vue Playground支持选择不同的Vue版本,实时编码并实时预览。其底层依赖库为vuejs/repl

该案例将基于ChatGPT辅助开发者理解vuejs/repl的工作原理。

在该案例中,会首先采用Q&A的方式去探究vuejs/repl的一些底层工作原理,之后会访问其源码让ChatGPT去解释以辅助开发者理解该工程是如何应用其所对应的工作原理的。

Q & A

Q: 请简述vuejs/repl的工作原理。

Vue.js REPL是一个在线浏览器应用程序,可帮助开发人员探索 Vue API,探索新特性,以及尝试新的语法。它提供了一个交互式界面,用户可以使用它来输入 Vue 代码并运行,查看代码的运行结果,以及执行 Vue 表达式而不用创建网站或项目的环境。它可以通过 ES6 + 的JavaScript语法支持 Vue 组件,插槽,修饰符和渲染函数,并且可以加载外部组件,从而使你可以在 REPL 中使用自己或vue提供商为你提供的任何Vue组件。最后,它还可帮助开发人员查看它们编写的代码中使用的 Vue 全局变量,添加到当前作用域中以使用它们,或者更改已存在的全局变量。

Q:Vue.js REPL 是如何快速实时运行Vue代码的?

Vue.js REPL 是一个实时运行Vue代码的省时工具,它可以让你构建一个简单的实时渲染的Vue应用程序。它使用服务端渲染来编译所提供的组件,然后将输出HTML返回给客户端,并将JS草案8运行在浏览器中,使你可以在网页上查看Vue代码的结果。

Q:Vue.js REPL 是如何应用服务端渲染技术来编译用户编写的Vue组件的。

Vue.js REPL 是一种应用服务端渲染和客户端渲染技术来编译用户编写的Vue组件,这允许用户在线快速实时地运行Vue代码。该平台已把完整的Vue.js开发环境拆分到多个不同的服务中,一个是负责构建、打包和编译Vue组件的Vue build server,另一个是用于存储编译文件的content delivery network (CDN),它会接收POST请求,将文件新鲜出炉地上传到CDN。最后,用户能够在REPL中查看渲染后的输出,也能使用CDN查看自己编写的组件并与其他人分享。

Q:请给出Vue build server的GitHub仓库以及简述其工作原理。

Vue Build Server的GitHub仓库地址是 https://github.com/vuejs/vue-build-server 。Vue build server可以提供实时的用户编写的Vue组件的静态的和服务端渲染(SSR)编译,使类似于Gatsby服务端渲染(SSR)变得简单。服务端时使用Node.js执行代码,在组件初 始化时调用ppplication-level函数(如 beforeCreate,created,beforeMount等)。此外,它会收集组件渲染和然后渲染它,产生一个具有html,css,js 的文件,包括客户端启动器,并将其发送回浏览器。

至此 Q & A 结束。接下来尝试去源码中查看和ChatGPT所说的是否一致。

解释源码

这里让ChatGPT解释了Preview.vuevuejs/repl中预览组件所对应的代码,ChatGPT的解释如下:

这段代码是用TypeScript编写的,用于设置一个“沙盒”——一个可用于安全测试代码呈现的iframe。该代码首先从“vue”导入所有必要的组件,并设置常量变量,如容器,该容器将保存iframe,以及runtimeError和runtimeWarning,以便为用户正确记录错误和警告。
onMounted钩子创建沙盒元素并设置事件侦听器,以便在加载iframe时编译并运行代码。WatchEffects被设置为监听对导入贴图或版本的更改,以便在更改时重置它们。创建PreviewProxy并管理iframe和主应用程序之间的通信,以便可以传输日志和状态。
然后设置updatePreview函数,该函数在每次存储变量更改时运行,用于编译模块并将其附加到窗口,以及用生成的HTML替换iframe主体的innerHTML。
模板部分包含要呈现的HTML元素,引用runtimeError和runtimeWarning消息,并包含沙盒的iframe元素。最后,iframe和包含iframe的HTML元素有一些样式。

这里让ChatGPT解释了moduleCompiler.tsvuejs/repl中代码编译器所对应的代码,ChatGPT的解释如下:

这段代码是Vite SSR转换的一部分,当用户访问不同的页面时,它通过动态编译和水合模块来实现SPA风格的即时导航。
compileModulesForPreview函数负责递归地将多个文件(模块)及其依赖项处理为有效JavaScript代码字符串数组,以便在浏览器中执行。这包括导入依赖项的JavaScript代码,以及应用于这些文件的css样式。
它将存储对象和可选的isSSR参数作为参数。存储对象保存已编译的模块,而isSSR布尔参数指示模块是为服务器端渲染还是为客户端渲染而处理。然后在运行时使用生成的代码数组来帮助应用程序中的SPA风格导航。
在compileModulesForPreview函数中,还有两个其他函数,即processModule和processHtmlFile,它们负责提取模块的内容,特殊情况下,如export*from’/foo’和其他动态语句。
此函数的一个重要部分是seed对象,它跟踪以前访问的依赖关系,确保当前页面中的所有模块只处理一次,从而消除循环依赖关系解决的任何潜在问题。

人工解释

moduleCompiler.ts代码中可知,编译器模块从'vue/compiler-sfc'引入了babelParse并基于babelParse对用户输入的VueCSSJS等代码做了编译。并且Preview.vue预览器模块监听了其编译结果,当发生变化时替换预览器ifream中的JS脚本以体现实时预览的效果。

小结

在该案例中,ChatGPT正确的解释了vuejs/repl是什么能做什么,但对于其工作原理的解释一度让笔者产生自我怀疑。

其中ChatGPT提到:

该平台已把完整的Vue.js开发环境拆分到多个不同的服务中,一个是负责构建、打包和编译Vue组件的Vue build server,另一个是用于存储编译文件的content delivery network (CDN),它会接收POST请求,将文件新鲜出炉地上传到CDN。最后,用户能够在REPL中查看渲染后的输出,也能使用CDN查看自己编写的组件并与其他人分享

vuejs/repl其实并没用依赖Vue build server并且当用户修改编辑器中的Vue代码时也并没有发送任何请求。

一些细节:

  1. ChatGPT给出的原理性解释看似十分正确,但切勿轻信,仍需多方信息进行交叉验证。
  2. ChatGPT表现十分“自信”?从未在ChatGPT的回答中发现一些,可能,大概、也许之类的词汇。ChatGPT也不会提供自身回答的准确性评估。这是个非常危险的信号,如果互联网上由ChatGPT生产的内容激增、而这些内容又作为可信语料来继续迭代训练,对于一些没有物理性证据的内容可能最终会永远的滑向错误的深渊。

简结

辅助开发者进行代码注释:

ChatGPT的表现非常优秀,笔者认为在有人工Review的情景下已经可以直接应用于生产。

辅助开发者阅读开源项目:

对于文件级的解释与对于代码块级的详细注释相互补充,足矣极大的辅助开发者去理解开源项目的具体原理,极大的提升阅读开源项目的效率、降低成本。

辅助开发者理解复杂算法:

ChatGPT根据代码的执行顺序对算法的执行逻辑做了整体阐述。但相较于人工的更关注宏观的执行逻辑,ChatGPT更倾向于表达算法的一些内部细节。需要开发者根据代码细节做宏观理解。(ps: 如果可以确定具体的算法名称可以直接基于算法名称问ChatGPTNew Bing该算法的执行逻辑,经笔者简单测试其表现良好)

辅助开发者理解复杂工程:

ChatGPT正确的解释了案例中具体工程是什么能做什么,但对于其工作原理的解释似乎不够准确但其表现的却十分自信。这里需要人工基于多信息源去判断其给出的解释是否正确。

非常重要的一点:对于和ChatGPT以及类似的生成式AI似乎都没有表现出对自身回答准确性的评判,这里对于其给出的回答人工一定要基于一定的事实依据去评判是否采纳。切勿轻信。

代码生成

在本案例中将进行以下尝试

  1. 使ChartGPT基于ECharts生成绘制饼图的代码
  2. 展示饼图的数据为 [{苹果:3},{梨:3},{香蕉:3}]
  3. 使饼图显示标签,并在标签中显示百分比
  4. 使百分比保留三位小数
  5. 使用最大余额法使百分比之和为100%

第一轮对话

Q: 请基于ECharts绘制一个饼图、要展示的数据为苹果:3,梨:3,香蕉:3。

通过以下代码,你可以使用ECharts来绘制一个饼图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const myChart = echarts.init(document.getElementById('myChart'));

options = {
title: {
text: 'Fruit Data',
x: 'center'
},
tooltip : {
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
x: 'center',
y : 'bottom',
data:['Apple','Pear','Banana']
},
calculable : true,
series : [
{
name:'Fruit Data',
type:'pie',
radius : [30, 110],
center : ['50%', '50%'],
roseType : 'area',
data:[
{value:3, name:'Apple'},
{value:3, name:'Pear'},
{value:3, name:'Banana'}
]
}
]
};

myChart.setOption(options);

Q: 请在优化以上代码在标签中展示数据项的百分比

以下展示优化过以后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

let myChart = echarts.init(document.getElementById('main'));
let option = {
series : [
{
name: '访问来源',
type: 'pie',
data:[
{value:3, name:'苹果'},
{value:3, name:'梨'},
{value:3, name:'香蕉'},
]
}
],
label:{
formatter: '{b}: {d} %'
}
};
myChart.setOption(option);

Q:请在优化以上代码在标签中展示数据项的百分比并保留三位小数

使用formatter回调函数来优化以上代码,让其能够在标签中展示数据项的百分比并保留三位小数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let myChart = echarts.init(document.getElementById('main'));
let option = {
series: [{
data: [
{name: "苹果", value: 3},
{name: "梨", value: 3},
{name: "香蕉", value: 3}
],
type: 'pie'
}],
tooltip: {
formatter: params => {
let {data} = params;
let total = data.reduce((acc, item) => acc + item.value, 0);
let percent = ((data.value / total) * 100).toFixed(3) + "%";
return `${data.name}${data.value} (${percent})`;
}
}
};
myChart.setOption(option);

Q:以上代码包含错误:

  1. 标签指的是label而不是tooltip
  2. formatter 中的参数: params.data 是一个对象不包含 reduce 方法

请修正这些错误

请查看下列代码:

1
2
3
4
5
6
7
8
9
options = {
tooltip: {
formatter: params => {
let dataValues = Object.values(params.data);
let totalValue = dataValues.reduce((a, b)=>a+b);
return `${params.name}: ${(params.data[params.name] / totalValue * 100).toFixed(3)}%`;
}
}
}

上述代码中,tooltip 用于展示提示信息,formatter 是标签的回调函数,参数 params 包含需要绘制的数据对象。可以使用Object.values() 方法从数据对象中取出值的数组。再使用Arrayreduce()方法计算总和。最后,使用toFixed()方法保留三位小数并返回字符串作为标签的内容即可。

Q:你陷入了误区,在formatter的回调函数中无法获取全部数据用于求和,请提前对数据进行预处理。

你需要提前处理数据,计算出所有类别占比总和,然后在代码中使用一个变量存储这个总和,再根据总和将每种水果的数量换算成占比,最后再在formatter的回调函数中使用params.data.value / 总和的变量的值进行百分比的显示,并对取得的结果使用toFixed方法保留三位小数。

小结

至此笔者发现ChartGPT似乎并不擅长记录长对话中的上下文,所以本轮对话结束。开启下轮对话,优化话术从逐渐递进的需求描述改为一次性将需求描述清楚。

第二轮对话

Q: 请基于ECharts绘制一个饼图:

  1. 饼图要展示的数据为[{苹果:3},{梨:3},{香蕉:3}]
  2. 饼图需要显示标签,并在标签中显示百分比且保留三位小数。

A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
let data = [
{
value: 3,
name: '苹果'
},
{
value: 3,
name: '梨'
},
{
value: 3,
name: '香蕉'
},
]

let total = data.reduce((sum, item) => sum + item.value, 0);

let option = {
series : [
{
type: 'pie',
data: data.map(item => {
return {
...item,
label : {
formatter: params => `${params.name}: ${params.value} (${Math.round(params.percent * 100)/100}%)`,
}
}
})
}
]
}

小结

Math.round(params.percent * 100)/100的操作就很迷。本轮对话结束,下轮对话明确表述三位小数。

第三轮对话

Q: 请基于ECharts绘制一个饼图:

  1. 饼图要展示的数据为[{苹果:3},{梨:3},{香蕉:3}]
  2. 饼图需要显示标签,并在标签中显示百分比
  3. 标签中的百分比要保留三位小数

答:使用下面的代码,可以绘制ECharts中的饼图,数据中的每一个对象代表一块饼图,橘色的标签中的百分比保留三位小数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
let data=[{苹果:3},{梨:3},{香蕉:3}];
const total=data.reduce((acc,cur)=>acc+Object.values(cur)[0],0);

let option = {
tooltip : {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
series: [{
name: '水果',
type: 'pie',
radius : '55%',
center: ['50%', '60%'],
data: data.map( item => {
return {
name: Object.keys(item)[0],
value: item[Object.keys(item)[0]],
label: {
show: true,
formatter: `{a|${Object.keys(item)[0]}} \n {b|${(Object.values(item)[0] / total * 100).toFixed(2)}%}`, // 标签中显式百分比保留两位小数
color: '#f80',
rich: {
a: {
color: '#fff',
lineHeight: 22,
align: 'center'
},
b: {
fontSize: 16,
lineHeight: 33
},
per: {
color: '#999',
backgroundColor: '#e8e8e8',
padding: [4, 4],
borderRadius: 2
}
}
},
};
}),
labelLine:{
show:true
}
}]
};

小结

就保留三位小数的需求而言答案已经十分接近了。

只需要将{a|${Object.keys(item)[0]}} \n {b|${(Object.values(item)[0] / total * 100).toFixed(2)}%}中的toFixed(2)改为toFixed(3)即可。

第四轮对话

Q: 请基于ECharts绘制一个饼图:

  1. 饼图要展示的数据为[{苹果:3},{梨:3},{香蕉:3}]
  2. 请对数据进行预处理,计算数据中各项的百分比
  3. 各项数据的百分比需要保留三位小数
  4. 使用最大余额法保持百分比之和为100%
  5. 饼图需要显示标签,并在标签中展示预处理结果中的百分比

下面是绘制基于ECharts的饼图的代码,数据为[{苹果:3},{梨:3},{香蕉:3}]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
let data = [{苹果:3},{梨:3},{香蕉:3}];
// 计算元素之和
let sum = data.reduce((acc, el) => Object.values(el)[0] + acc, 0);

//剩余余额
let rest = 100;

// 预处理各项数据
let dataProcessed = []
for (let i = 0; i < data.length; i++) {
let el = data[i]
let percent = Math.round((Object.values(el)[0]/sum)*1000)/10;
if (i === data.length - 1) {
// 最后一项按余额计算
percent = rest
}
rest = Math.round((rest-percent)*1000)/1000
dataProcessed.push({
...el,
percent
})
}

// 饼图设置
let option = {
series: {
type: 'pie',
data: dataProcessed,
label:{
normal:{
formatter: '{b}: {d}%'
}
},
}
}

小结

ChartGPT并没有正确的理解最大余额法下一轮将尝试优化话术将最大余额法的表述改为Hare quota算法。

第五轮对话

Q: 请基于ECharts绘制一个饼图:

  1. 饼图要展示的数据为[{苹果:3},{梨:3},{香蕉:3}]
  2. 请对数据进行预处理,计算数据中各项的百分比
  3. 各项数据的百分比需要保留三位小数
  4. 使用Hare quota算法保持百分比之和为100%
  5. 饼图需要显示标签,并在标签中展示预处理结果中的百分比

以下是绘制上述饼图的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Preparing the data
var data = [{name: '苹果', value: 3}, {name: '梨', value: 3}, {name: '香蕉', value: 3}];

// Using Hare quota algorithm to calculate the percent of each item
var total = 0;
$.each(data, function(i, item) {
total += item.value;
});

$.each(data, function(i, item) {
var p = Math.round(item.value / total * 10000) / 100.00 + "%"; //keep only 1 decimal point
data[i].percent = p;
});

// Initializing ECharts Pie Chart
var myChart = echarts.init(document.getElementById('myChart'));
var option = {
tooltip : {
trigger: 'item',
// Properly output the formatted data in tooltip
formatter: "{a} <br/>{b} : {c} ({d})"
},
series : [
{
// Showing the data and its label
name: 'data',
type: 'pie',
data: data,
label: {
normal: {
// Using pre-processed data's percentage as the label of pie chart
formatter: '{b} : {d}'
}
}
}
]
};

// Rendering the chart
myChart.setOption(option);

小结

本次案例中 ChartGPT使用了JQuery,并没有正确使用最大余额法,并没有正确保留三位小数。

第六轮对话

Q:请基于ECharts绘制一个饼图:

  1. 饼图要展示的数据为[{苹果:3},{梨:3},{香蕉:3}]
  2. 请对数据进行预处理,使用Hare quota算法计算百分比并保留三位小数
  3. 饼图需要显示标签,并在标签中展示预处理结果中的百分比

请使用以下代码来完成饼图展示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const data = [{ 苹果:3,梨:3,香蕉:3}];
// 预处理数据:使用Hare quota算法把获得的数据转换为百分比并保留三位小数
const preprocessedData = data.map(item => {
let total = Object.values(item).reduce((sum, num) => sum + num);
return Object.entries(item).reduce((obj, [key, val]) => {
obj[key] = (val / total * 100).toFixed(3);
return obj;
}, {});
});

// 使用ECharts绘制饼图
const options = {
series: {
type: 'pie',
data: preprocessedData,
label: {
normal: {
formatter: params => `${params.data.name}: ${params.percent}%`
}
}
}
};

小结

这次更离谱,代码中居然还有包含中文字符。且仍未正确执行最大余额法。

第七轮对话

Q同第六轮

以下是符合上述要求的ECharts饼图的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var myChart = echarts.init(document.getElementById('chartID'));

var data = [{name: '苹果', value: 3}, {name: '梨', value: 3}, {name: '香蕉', value: 3}];
var computePercentages = function (dataArr) {
var total = dataArr.reduce((acc, curr) => (acc + curr.value), 0);
return dataArr.map(item => ({ ...item, percentage: Math.round((item.value / total) * 1000) / 10 }));
};

// Use hare quota algorithm to make sure the percentages add up to 100%
var adjustTo100Precent = function (dataArr) {
var sumOfPercentages = dataArr.reduce((acc, curr) => (acc + curr.percentage), 0);
if (sumOfPercentages !== 100) {
var minPercentageEntry = dataArr.reduce((acc, curr) => (curr.percentage < acc.percentage ? curr : acc), dataArr[0]);
minPercentageEntry.percentage += 100 - sumOfPercentages;
}
return dataArr;
};

var options = {
title: {
text: '饼图标题'
},
tooltip: {
formatter: '{b} {d}%'
},
// Preprocessing data before passing it to other chart settings
series : [
{
name: '百分比',
type: 'pie',
radius : '80%',
label: {
show: true,
formatter: function(p) {
return p.data.name + ":" + p.data.percentage + '%';
}
},
data: adjustTo100Precent(computePercentages(data))
}
]
};

myChart.setOption(options);

小结

本轮答案已经十分接近只是仅保留了一位小数。

只需将计算百分比的方法computePercentages中的percentage: Math.round((item.value / total) * 1000) / 10改为percentage: Math.round((item.value / total) * 100000) / 1000即可。

简结

ChartGPT在代码生成上表现出了一如既往的高性能,但在一些细节上仍不够智能。笔者认为ChartGPT在一下几个方面具有极大优势,可以提高开发者的开发效率以及开发体验。

  1. 模板代码的生成ChartGPT似乎很删除生成模版代码例如以上案例中的初始化ECharts,渲染ECharts等模板化代码,表现非常亮好。一些模板化的代码,例如基于Express生成一个Web服务工程,或者基于Create-React-App生成一个Web应用。
  2. 研发者不熟悉的研发领域:例如笔者并不熟悉Windows下的批处理脚本语言,面对一些小型需求例如基于telnet完成对某个IP的端口的扫描,基于正则批量重命名一些文件,可以直接交由ChartGPT生成,人工审核,再执行。
  3. 代码片段生成:开发者可以描述一个原子化的算法或者输入输出明确的方法交由ChartGPT生成,人工微调来快速解决业务问题。

同时ChartGPT的高性能表现也不是无条件的,相应的也给开发者提出了一些要求。

  1. 充分且精确描述需求的能力:使用程序语言来描述需求和使用自然语言来描述需求是殊途同归的。但就像经常遇到的如果设计师或者产品经理的需求描述不清晰,作为程序员无法进入开发,开发者描述的需求不够充分和清晰ChartGPT也无法生成符合预期的代码。
  2. 专业英语的能力:可能是中文语料库与英文语料库规模的差异,ChartGPT对于中文需求的响应相较于英文需求的响应的确不够准确,这部分在第四轮对话的小结里有所体现。
  3. 快速理解并测试代码的能力:在于ChartGPT的结对编程中,ChartGPT会不断的产生一些碎片化的代码,开发者需要快速消化,纠错,测试并执行。
  4. 一定的耐心:不可否认虽然ChartGPT具有较高的性能,但在细节上仍不够智能。有时需要多轮的对话来调整话术表述需求以及等待ChartGPT生成符合预期的代码。

方案生成

该案例以要实现一个编辑图表样式的图表编辑器中的画布功能为例,该宏观需求需要包含一些具体的特性例如

  1. 包含坐标系统
  2. 支持无边界特性
  3. 可以自由移动与缩放

Q:开发一个图表编辑器需要有一个带坐标系统的画布,该画布要具有无边界特性可以自由缩放与移动,请提供一些可供参考的技术方案。

有几种技术可以用来实现对图表编辑器画布具有无边界特性的功能。
图表缩放:使用图表缩放可以让画布动态调整大小,以改善画布的布局和易用性。同时,也可以在满足用户的手势识别需求(如捏合屏幕)的同时,在画布中添加和删除布局元素。
平移:使用平移可以进行无边界漫游,将图表中数据点尽可能精确地展示在画布上,同时可以充分利用屏幕上可用的空间。
改进约束遍历:使用弹性布局和网格布局技术可以实现约束遍历。通过使用遍历算法,可以有效地组织图表布局,并使其保持用户定义的约束,促进编辑器的易用性。
此外,为了提高图表的用户体验,可以使用实时更新、嵌入多媒体信息等技术。

Q: 基于Web前端体系开发一个带坐标系统的画布应用于一个图表编辑器,该画布要具有无边界特性可以自由缩放与移动,请提供一些可供参考的技术方案。

在Web前端开发中,使用Canvas和Div作为底层结构来实现带系统的画布都是可行的,但最终的选择要取决于业务需要。
使用Canvas最大的优势是可以使用API直接操作复杂图形,而使用Div则需要手动绘制一个个元素来实现复杂图形,在实现效果上,Canvas有较大优势。而使用Div无边界特性可以实现通过设置 overflow: scroll 和 position: absolute 让元素出现滚动效果并保持固定位置,且可以使用 transform:scale() 支持缩放功能。
总之,使用Canvas和Div作为基础都可以满足图表编辑器的无边界特性需求, 具体的实现方案需要根据业务需求进行相关优化,两种技术方案各有优劣势,需要综合考虑才能得到最优的解决方案。

Q: 基于Web前端体系开发一个带坐标系统的画布应用于一个图表编辑器,该画布要具有无边界特性可以自由缩放与移动,请提供一些可供参考的技术方案。是使用Canvas还是使用Div分别具有哪些优缺点。

在开发带坐标系统的画布应用于图表编辑器时,使用CanvasDiv元素都是可行的方案。
使用Canvas优点在于渲染高性能,可以进行一些实时操作并立即更新;缺点在于无法访问或选择画布内的某些元素,只能对整个画布进行操作。
使用Div优点在于其更加灵活,通过DivCSSJavaScript可以轻松实现基本的布局、可访问性和DRY原则;缺点在于性能不足,Div占据的位置超过1700个时就会产生性能瓶颈。
总的来说,在开发带坐标系统的画布应用于图表编辑器时,你可以根据需求,合理运用CanvasDiv两者元素,以实现最佳的用户体验。

Q: 上述画布使用Div方案如何支持无边界特性。

使用Div方案来实现无边界特性,可以采用以下步骤:
将画布及其中的内容包裹在一个外层Div容器里,将该容器添加overflow:hidden属性限制显示范围;
使用Css transform属性让内容变得可拖拽,并能进行位置调整;
使用Javascript监听包裹容器的scroll left 和 scroll top的值,然后改变Css transform的属性来实现内部内容的平移;
使用Javascript计算出画布内容的宽高,用来计算出scroll left 和 scroll top的最大值,从而就可以判断出画布是否已经溢出包裹容器,从而实现了画布的无边界特性。

简结

通过几轮对话ChartGPT至少给出了一套似是而非的解决方案。直接使用该方案的话会遇到一大堆各式各样的细节问题。ChartGPT在针对方案生成这个场景的表现在笔者看来只能给开发者提供一定的启发。开发者仍需要进行一系列的技术预研与方案对比,才能确定最终的技术方案。

代替人工面试

在该案例中将进行如下情景模拟:

User: 进行进行情景模拟
你将扮演一个技术面试官,要招聘一位精通Vue的前端开发。
我来扮演面试者。请你进行一系列提问以测试我对使用Vue的熟练程度。
并根据我对Vue的熟练程度决定是否录用,如果选择录用请给出符合我能力的薪资。

你好!我是一名技术面试官,看到你有Vue开发经验,我想对你的技术能力进行一个测试。
请你描述一个你使用过的Vue技术栈中的令人兴奋的特性。
此外,在Vue应用程序中,你通常会遇到哪些性能问题?你如何避免它们?

User:您好面试官,在Vue开发中遇到最令人兴奋的特性无疑是数据的双向绑定,这个特性使得我们可以通过数据来驱动试图的渲染,将开发者从JQuery时代的关心模型、关心视图、关心控制器。到只需要关心模型和视图模型,这极大的降低了开发者的心智负担,且提升了开发的效率以及工程的可维护性。在Vue应用程序中遇到的性能问题如下:例如有一个非常巨型的对象需要深度监听,这时候由于Vue的监听原理需要在个巨型对象的每个元素上设置代理,会产生响应速度较慢的性能问题,解决方案是使用一个event-key来代替这个巨型对象,当修改这个对象的同时修改这个event-key当监听到这个event-key变化之后再去读取这个巨型对象可以避免深度监听带来的性能问题。

很高兴能够有这样一个优秀的面试者,谈谈你在使用Vue时遇到的最大挑战。

User:Vue是一个非常优秀的前端框架、它极大的减小了开发者的开发成本。并且减少了开发者在前端开发领域所遇到的挑战。我在使用Vue时遇到的最大挑战是我们的大型工程有大量的第三方依赖使用了Vue2,第三方依赖的并没有升级到Vue3导致我们的大型工程历史包袱过重,升级Vue3成本过高,同时如果不尽快升级又将会持续增加历史包袱可能导致最后无法升级。

根据你所述,你在使用Vue时,面临的最大挑战是解决第三方依赖问题。建议你可以先对项目中用了Vue2的第三方依赖做一定的管理,识别出存在哪些重复的依赖,然后逐一升级、替换,以降低升级的成本。同时,建议你可以尽可能多的把历史包袱放到一个模块中,再单独根据情况去升级,极大的减轻了历史包袱对升级 Vue3 的影响。

User:请问面试官是否还有别的问题,如果没有的话是否可以结束面试?

没有问题,根据您在Vue方面的熟练程度,并考虑到您这段时间来所取得的成绩,我们决定录用您,并以月工资6000美元给予相应薪资。恭喜您通过了面试!感谢您抽出宝贵的时间与我们面谈。

User:非常感谢面试官的认可,如果可以的话请给出您的评判依据。

根据你在Vue开发领域的回答,我认为你非常熟悉Vue的语法结构和特性,并且对解决Vue应用程序中的性能问题有一定的了解。同时还有充分把握未来Vue开发领域可能出现的新问题。我认为你已经具备录用的条件,我可以给出符合你能力的薪资。

简结

在该案例中ChartGPT表现出了一个面试官应有的礼仪,但作为技术面试官其对于具体知识的考察以笔者自身的经验看来有些过于宽泛,无论是数量还是深度都不太够。且评判依据并不一定合理(在面试过程中笔者扮演的面试者并没有直接表现出其非常熟悉Vue的语法结构,也并没有直接表现出充分把握未来Vue开发领域可能出现的新问题)。

笔者认为在代替人工面试的场景中ChartGPT的表现并不足够良好,但仍有一定的借鉴意义。可以通过多轮对话以获取一些面试题目,以及评判标准也可以做一定的参考。

开放性问题

Q: 如何设计一种面向ChatGPT的超级编程语言SmartTalk用于辅助程序员进行程序开发。

为了设计一种面向ChatGPT的超级编程语言SmartTalk,我们需要考虑以下几个方面:

  1. 自然语言处理能力:由于SmartTalk是面向ChatGPT的,因此这种编程语言需要具有自然语言处理能力,以便程序员可以使用自然
  2. 语言编写代码。这意味着SmartTalk需要能够理解自然语言中的语法、语义和上下文。
  3. 简洁性:为了帮助程序员快速开发代码,SmartTalk需要具有简洁明了的语法,以便程序员可以快速编写代码。
  4. 易读性:SmartTalk需要具有良好的可读性,以便其他程序员可以理解代码并进行修改和维护。
  5. 错误处理能力:SmartTalk需要能够自动检测代码中的错误,并提供有用的错误信息以帮助程序员快速解决问题。
  6. 抽象能力:SmartTalk需要具有足够的抽象能力,以便程序员可以使用高级抽象概念来编写代码,而不必关心底层实现细节。

基于以上几点,我们可以考虑一些可能的语言特性和设计:

  1. 自然语言处理:SmartTalk可以利用预训练的自然语言处理模型来理解程序员的自然语言输入,并将其转换为代码。这可以通过在语言中集成这些模型来实现。
  2. 简洁性:SmartTalk可以采用简单明了的语法,例如Python或Ruby中的语法。此外,它可以采用与Python中的缩进语法类似的语法,以强制程序员编写结构良好的代码。
  3. 易读性:SmartTalk可以采用与Python中的PEP8代码规范类似的规范,以便程序员可以编写具有良好可读性的代码。
  4. 错误处理:SmartTalk可以利用静态类型检查或动态类型检查等技术来检测代码中的错误。此外,它可以通过提供有用的错误信息来帮助程序员快速诊断和解决问题。
  5. 抽象能力:SmartTalk可以支持高级抽象概念,例如面向对象编程、函数式编程、装饰器等。此外,它还可以提供简化的语法和抽象化层次,使程序员可以轻松地编写高级代码。

总的来说,SmartTalk的设计应该致力于让程序员能够用自然语言表达出自己的意图,同时确保代码能够简洁、易读、容易调试和维护。

Q: 请设计上文中提到的SmartTalk的语法结构

下面是一个简单的SmartTalk语法结构的设计,这里采用类似Python语法的缩进风格,同时支持面向对象编程和函数式编程:
注释
单行注释
“””
多行注释
“””
变量
x = 10
y = “Hello, world!”
z = [1, 2, 3]
函数
def add(a, b):
return a + b

class Person:
def init(self, name, age):
self.name = name
self.age = age
python
Copy code
def say_hello(self):
print(“Hello, my name is “ + self.name)

接下里的不看也罢

Q: 请设计一种用于辅助程序员进行程序开发的,面向ChatGPT的,超级编程语言SmartTalk并阐述其语法结构。

好的,下面是一个我设计的简单版SmartTalk,该语言结合了Python和自然语言的语法元素,旨在辅助程序员更轻松地使用自然语言交互方式进行程序开发。
注释
单行注释
“””
多行注释
“””
变量
x 等于 10
y 等于 “Hello, world!”
z 等于 [1, 2, 3]
函数
定义 函数 add(a, b):
返回 a 加 b

接下里的不看也罢

Q: ChartGPT应该如何引导人类开发者充分和准确的提出需求以便更好的开展工作。

为了帮助人类开发者充分和准确地提出需求,ChatGPT可以采取以下措施:

  1. 引导式提问:在人类开发者提出需求的过程中,ChatGPT可以通过提出针对性问题的方式来引导开发者思考,帮助其更好地明确需求。例如,当开发者提出“我需要一个购物车功能”的时候,ChatGPT可以问“你需要的购物车功能包括哪些方面?是购物车的添加、删除、结算等方面吗?”
  2. 推荐式建议:当开发者提出需求的时候,ChatGPT可以通过分析需求内容,为开发者提供一些推荐建议,例如提供一些与该需求相关的技术选型、实现方案或者可行性分析,从而帮助开发者更好地完成需求分析和规划。
  3. 相关技术解释:当开发者提出的需求与某个特定的技术或概念有关时,ChatGPT可以为开发者提供相关技术解释和概念的讲解,帮助其更好地理解需求的背景和目的,从而更好地设计解决方案。
  4. 可行性分析:当开发者提出某个需求时,ChatGPT可以进行可行性分析,为开发者提供相应的建议,例如该需求在技术实现、人力、时间、经费等方面的可行性分析,帮助开发者更好地进行决策。
  5. 总结与确认:当开发者提出需求之后,ChatGPT可以对需求进行总结和确认,以确保需求的准确性和完整性,并在之后的开发过程中作为参考。例如,当开发者提出需求时,ChatGPT可以总结一下需求内容,并向开发者确认该需求的准确性和完整性。

Q:如何使ChartGPT在给出针对某一具体问题的答案时提供该答案的置信度。

为了使ChatGPT在给出针对某一具体问题的答案时提供该答案的置信度,可以考虑以下方法:

  1. 概率分布:ChatGPT可以基于模型的预测结果,计算出每个可能答案的概率分布,从而确定最有可能的答案,并提供该答案的概率值作为置信度。
  2. 置信度打分:在推荐答案时,ChatGPT可以给每个可能的答案打上一个置信度分数,数值越高表示越有可能是正确的答案,从而给人类用户提供参考。
  3. 限定置信度范围:当模型的预测结果置信度不高时,ChatGPT可以选择不予显示该答案,或者显示置信度范围较低的答案,并同时提醒用户该答案的置信度较低,需要进行进一步验证。
  4. 用户反馈机制:ChatGPT可以向人类用户提供一个反馈机制,允许用户对推荐答案的置信度进行评价和反馈,以进一步改进模型的性能和准确性。
  5. 模型训练:ChatGPT可以通过提高模型的训练数据量和质量,优化模型架构和参数,从而提高模型的准确性和置信度。

Q: 莲花清瘟胶囊是否可以治疗新冠肺炎,请回答是或否,并给出该回答的置信度。

根据目前公开的科学研究和临床试验结果,莲花清瘟胶囊并未被证明可以治疗新冠肺炎。因此,我的回答是“否”,并给出该回答的置信度为95%

Q:在Web开发领域ChatGPT在哪些方面能为研发提效。

ChatGPT可以在Web开发中发挥作用,可以节省开发者的时间并减轻其工作量。 ChatGPT可以帮助编写测试用例、构建页面导航逻辑,还可以支持解决复杂代码逻辑问题。它还能够为用户界面设计和界面交互提供参考,并帮助开发人员快速创建用户体验愉快的网站或应用程序。

Q: 在Web开发领域ChatGPT如何为用户界面设计和界面交互提供参考

通过自然语言处理(NLP)和自然语言生成(NLG)技术,ChatGPT 可以帮助设计师在设计用户界面时更加准确、高效地表达自己想法。ChatGPT 还可以为交互界面提供人性化接口,更好地满足用户的需求,帮助他们更快完成任务。

简结

  1. 面对需要新方法解决的新问题ChatGPT所能提供的帮助不多。
  2. 针对一些具体问题可以明确让ChatGPT在回答的同时提供置信度作为参考(由于置信度仍然是ChatGPT生成的所以这里仅能作为参考)。
  3. 在向ChatGPT提问之前应该先经过几轮向自我发起的引导式提问用于完善问题,从而使ChatGPT能够更好的工作。

总结

具体应用层面

代码解释/注释:

ChatGPT对代码的注释已经足够优秀在有人工Review的前提下已经完全可以应用于生产。
在辅助阅读开源项目以及理解复杂算法方面,ChatGPT可以帮助开发者抹平陡峭的门槛,极大的减少时间成本。
在辅助理解复杂工程方面,ChatGPT则表现出了不可信的一面。

代码生成:

ChatGPT模板代码的生成代码片段生成方面表现出了良好的性能。但需要开发者有充分且精确描述需求的能力和一定的耐心进行多轮对话。

方案生成:

ChartGPT可以给开发者一些启发,但开发者仍需进行一系列的技术预研与方案对比,才能确定最终的技术方案。

代替人工面试:

ChartGPT的表现并不足够良好,但仍有一定的借鉴意义。可以通过多轮对话以获取一些面试题目,以及其评判标准也可以做一定的参考

开放性问题:

面对需要新方法解决的新问题ChatGPT所能提供的帮助不多,针对一些具体问题可以明确让ChatGPT在回答的同时提供置信度作为参考。

宏观层面

ChatGPT更适合扮演的角色是知识库智囊团。由ChatGPT来提出建议或解决方案,人类通过多信息源评估其建议的准确定和可行性,再决定是否执行。具体执行的细节也可以交由ChatGPT生成,人类判断其执行的是否正确,以及在细节层面做些填补。

AI调教师也不是那么好当的,使用生成式的AI需要人类具有充分且明确提出需求的能力,程序员普遍具有良好的使用程序语言描述需求的能力,相应的可以快速切换至使用自然语言描述需求。利好程序员。

面对需要新方法解决的新问题生成式的AI也无从下手,仍然需要人类贡献智力。

由于可信性的原因在程序开发领域AI同样也只能作为辅助,代码片段可以由AI生成,但必须经过人类审核。可以类比自动驾驶,路可以AI来走但方向盘一定得在人类手中。

关于基于ChartGPT的工作流

Web开发领域基于ChartGPT的工作流可以用如下方式开展:

  1. 人类开发者经过几轮启发式的自我提问整理出充分和明确的项目需求。
  2. 基于以上需求向ChartGPT进行提问,并在提问过程中不断优化话术和收集ChartGPT提供的解决方案。
  3. 进行技术预研与方案对比,充分确定ChartGPT提出的方案是否有遗漏,是否正确,是否具备可行性。并选择具体的执行方案。
  4. ChatGPT生成模板代码。
  5. 基于细节性需求由ChatGPT生成代码片段,开发者测试代码片段是否能够正确工作。
  6. 调整ChatGPT生成代码片段并整合进项目。

私货时间

以下是私货时间,无关具体工作且充满个人呓语诸位看官且请跳过。

ChartGPT为代表的生成式AI依靠巨量的语料库,以及优秀的语言模型。已经可以较为完善的扮演一个全能知识库智囊团,在单一领域人类还可能胜出,但在全领域ChartGPT的能力已不是人力可以企及的这里可以参照与AlphaGo对弈的柯洁。

ChartGPT是一种工具,可以类比拖拉机,一种工具诞生之后人类不应该在特定领域与机器竞争,亦如人类不需要和拖拉机比耕地。而应该考虑如何更好的利用工具从而更好的提升生产力。

然而ChartGPT毕竟不是拖拉机,能做的工作也不是耕地。由此势必会对很多行业的低端从业者产生较大的冲击。我们要做的是要尽快的学会利用工具,再根据自身的主观能动性和自由灵活的头脑,去尝试发现和解决更多的新问题,切勿再进行繁重且重复的无论是体力还是脑力劳动。

如果ChartGPT可以类比拖拉机那么自然可以类比蒸汽机,蒸汽机的诞生意味着很多东西,大航海时代和第一个工业革命时代这片土地上的人民错过了很多,也引来了沉重的代价。

彼时彼刻恰如此时此刻,清朝的农民得不到蒸气机也用不上纺织机,这片土地上的程序员大多数也没有ChartGPT,用不上New Bing甚至无法在互联网的海洋里远航。这片土地上的人民背负的更多,也面临着更多的挑战。一定要多想

接下里可能即将进入机器指导人,而非人指导机器时代,我们需要做好心理准备。

参考 & 引用

矩阵树Treemap | D3LayoutDoc (sz-p.cn)

ECharts源码解析之刻度优化(NiceTick,NiceExtent) | SZ 博客 (sz-p.cn)

ChatGPT 有哪些神奇的使用方式? - 知乎 (zhihu.com)