Web 和机器学习的人工智能辅助变成(二)
原文:
annas-archive.org/md5/f5ce3ca93c7ecd74cff0d3ae335a95cc译者:飞龙
第七章:使用响应式网页布局支持多个视口
简介
构建网页是一个挑战。你不仅需要用 HTML、CSS 和 JavaScript 来制作这些页面以执行你设定的任务,而且还需要确保它们对大多数用户都是可访问的。此外,你需要确保页面无论在 PC、平板电脑还是移动设备上都能良好渲染,这意味着你需要考虑屏幕尺寸;设备的方向,即横屏或竖屏;以及像素密度。
确保你的网页在许多设备上看起来很好有很多不同的技术,但这一切都始于有一个策略,一个根据使用的设备为用户提供的体验愿景。一旦你设定了这个愿景,你就可以开始实施它。
你需要做出的某些选择包括,如果你的内容以列的形式呈现,应该列出多少列。其他事物应该如何表现,比如填充和边距?内容应该是居中还是左对齐?内容应该是垂直堆叠还是水平排列?是否有在移动设备上应该隐藏的内容?正如你所看到的,有许多选择会影响你需要使用的提示。
在处理网页布局时使用 AI 助手可能会有所帮助,因为你需要记住很多信息,所以不仅 AI 助手可以记住所有这些细节以便于查找,而且你还可以利用它来建议不同的设计。
在本章中,我们将:
-
解释像视口和媒体查询这样的技术术语。
-
应用不同的技术来优化不同视口的渲染。
-
利用 Copilot 聊天功能来改进我们的代码。这是 GitHub Copilot 中你可以使用的“其他”模式;它是一个聊天窗口,让你可以输入提示并得到响应。这种体验类似于 ChatGPT 这样的 AI 工具。
商业问题:电子商务
本章将继续探讨在前三章中已经工作的电子商务用例。构建功能是一回事,但你必须假设你的用户会从许多不同的设备与你的网站互动,并且这种体验必须是好的,否则他们会去竞争对手的网站。
问题与数据域
现在有许多不同的设备:平板电脑、手机,从小型桌面屏幕到大型屏幕。像素密度各不相同。这不仅仅是将网站缩小或放大以适应新设备的问题,你可能还需要设计一个完全不同的体验,以更好地适应特定设备的视觉风格。还有其他一些考虑因素,比如如果我们假设设备有限制,比如它能处理多少并发下载以及可能具有的网络速度,我们希望向较小的设备发送多少内容。一个具有宽分辨率的桌面机器通常与互联网有很好的连接,这是很常见的。相反,一个移动设备可能处于 3G 网络或更差的网络,因此你需要通过要求更少的图形资源、更小的 JavaScript 包和更多来适应这种情况。
将问题分解为功能
在此之前的几个章节中,我们已经看到,一个好的方法就是确定我们需要实现的功能。这些功能更多地关乎确保设计和交互在优先设备上工作良好,而不是关于读写数据。因此,你可能会有一个如下所示的功能分解列表:
-
应在横幅模式下以双列设计渲染购物车页面。
-
竖幅模式:
-
应在竖幅模式下以单列设计渲染购物车页面。
-
应在屏幕底部显示菜单操作。
-
你应该隐藏某些功能,比如 X、Y、Z(假设 X、Y、Z 在宽屏桌面上有可用)。这个要求的目的在于你必须“重新思考”移动体验与桌面体验的区别,哪些功能是体验的核心,以及我们只有在有足够的屏幕空间来显示时才显示哪些功能。
-
应支持并渲染以下移动设备的视觉吸引力外观:iPhone X、Y、X 和 Android。
-
-
应在 3G 连接上在 1 秒内渲染页面。
如你所见,这些功能与用户体验的联系比任何数据领域都要紧密。
提示策略
我们的提示策略与之前类似,是一种混合方法,即使用编辑器内的体验,并在开放文本文件中添加提示,以在 Copilot 中打开聊天窗口;根据你的判断混合这些方法。
至于提示,这些提示中应有足够的内容,使 Copilot 意识到它需要为特定设备提出设计建议。因此,它应该能够从上下文中推断出哪些分辨率、像素密度和其他细节应该影响它即将生成的建议。至于使用的提示模式,我们将使用第二章中描述的“探索性提示模式”。
视口
那些只需要开发一个看起来在 PC 上很漂亮的网页的日子已经过去了。今天,你的网页可以在多种不同的设备上渲染,并且需要在所有这些设备上都看起来很好,否则你的客户可能会去其他地方。
理解如何构建网页的第一步是熟悉一些关键概念。第一个概念是视口。视口是用户可以看到的页面的一部分。视口与窗口之间的区别在于,视口是窗口的一部分。
根据所使用的设备,例如,桌面屏幕或移动设备,其大小会有所不同。当你编写代码以适应不同的大小,以便良好渲染时,这被称为使页面“响应式”。
媒体查询
好吧,我正在处理不同屏幕大小的设备,所以我如何编写代码以确保视觉界面适应我使用的设备大小?
答案是利用一个称为媒体查询的结构。媒体查询是 CSS 中的一个逻辑块,它识别一个特定条件,并在该条件为真时应用特定的 CSS。
假设存在以下代码;这基本上就是它的工作原理:
if(page.Width > 1024px) {
// render this UI so it looks good for desktop
} else {
// render this UI to look good for a mobile device
}
下面是一个媒体查询的示例:
body {
background: blue;
}
@media (max-width: 600px) {
body {
background-color: lightblue;
}
}
前面的代码识别了一个条件,即如果视口当前宽度最多为 600 像素(这对大多数移动设备来说是正确的),则将背景颜色设置为浅蓝色。
这个例子可能感觉有点牵强;为什么我会在移动设备上想要一个不同的背景颜色,而不是在正常的桌面设备上?你不会,但上面的例子给你一个关于媒体查询如何识别视口大小并在视口满足某些条件时应用特定 CSS 的想法。
何时调整不同视口并使其响应式
使用响应式设计的良好理由是,你有一个在桌面设备上看起来很棒的布局,但可能对移动设备来说太宽了。例如,假设你有一个带有左侧菜单和右侧主要区域的网页:
图 7.1:带有菜单和主要区域的页面
如果我们尝试在移动设备上查看此页面,它看起来不会很好。它看起来可能像这样:
图 7.2:移动设备上的非响应式页面
在上面,我们看到设备试图查看页面,但主要区域被截断了。在这个时候,作为设计师,你需要考虑如何解决这个问题。一种方法是通过使用 flexbox 或 grid 等技术垂直对齐内容。例如,菜单可以放在顶部,而主要区域放在底部。
检查此页面上使用的样式,你会看到以下 CSS:
<style>
/* container, grid, left col is menu, right col is main area */
.container {
max-width: 1200px;
margin: 0 auto;
display: grid;
/* 2 columns */
grid-template-columns: 200px 1fr;
background-color: #000;
padding: 10px;
/* space 5 px between columns */
grid-gap: 10px;
/* height 100% */
height: 100%;
}
在前面的代码中,你可以看到如何使用display:grid网格来创建布局:
/* menu items */
.menu {
/* background black shade */
background-color: darkslategrey;
padding: 0px;
/* column 1 */
grid-column: 1;
}
你还可以看到菜单是如何放置在左侧的,grid-column: 1:
/* menu item */
.menu-item {
padding: 5px;
background-color: darkslategrey;
height: 20px;
}
.menu-item a {
color: white;
text-decoration: none;
font-family: sans-serif;
}
/* main area, padding 5px, dark grey */
.main {
padding: 5px;
background-color: darkslategrey;
grid-column: 2;
color: white;
font-family: sans-serif;
}
主要区域是第二列,grid-column: 2:
</style>
为了改进此设计,使其能够在屏幕较小的设备上使用,例如移动设备,我们可以使用以下提示:
[提示]
如果是移动设备,将菜单设置为第 1 行,主要区域设置为第 2 行。
[提示结束]
结果 CSS 如下:
[提示响应]
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
grid-template-rows: 200px 1fr;
}
.menu {
grid-row: 1;
grid-column: 1;
}
.main {
grid-row: 2;
grid-column: 1;
}
}
[结束响应]
如你所见,Copilot 可以接受这个提示指令并相应地调整 CSS。代码将布局调整为单列两行。同时,它确保菜单位于顶部,主区域位于底部:
结果页面现在在移动设备上的渲染方式如下:
图 7.3:菜单和主区域在移动视口中垂直渲染
大多数浏览器都有内置的功能,允许你调整视口大小,这有助于你测试你修改过的 CSS。你还可以更改窗口大小。
用例:使我们的产品画廊响应式
在这个用例中,我们将使用一个在桌面渲染良好的电子商务应用程序,但在移动设备上不良好,并修复这个问题。
首先,这是它的 HTML 代码。如果你想跟上来,你可以将以下代码保存到products.html文件中:
<html>
<head>
<title>menu</title>
<link rel="stylesheet" href="css/style.css">
<style>
/* container, grid, left col is menu, right col is main area */
.container {
max-width: 1200px;
margin: 0 auto;
display: grid;
/* 2 columns */
grid-template-columns: 200px 1fr;
background-color: #000;
padding: 10px;
/* space 5 px between columns */
grid-gap: 10px;
/* height 100% */
height: 100%;
}
/* menu items */
.menu {
/* background black shade */
background-color: rgb(25, 41, 41);
/* background-color: #ddd; */
padding: 0px;
/* column 1 */
grid-column: 1;
}
/* menu item */
.menu-item {
padding: 5px;
background-color: rgb(25, 41, 41);
height: 20px;
}
.menu-item a {
color: white;
text-decoration: none;
font-family: sans-serif;
}
/* main area, padding 5px, dark grey */
.main {
padding: 5px;
background-color: rgb(25, 41, 41);
grid-column: 2;
color: white;
font-family: sans-serif;
}
/* if mobile, set menu to row 1 and main row 2 */
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
grid-template-rows: 200px 1fr;
}
.menu {
grid-row: 1;
grid-column: 1;
}
.main {
grid-row: 2;
grid-column: 1;
}
}
/* gallery, 2 columns per row */
.gallery {
display: grid;
/* horizontal grid */
grid-template-columns: auto auto auto;
grid-gap: 20px;
}
/* gallery item */
.gallery-item {
flex: 1 0 24%;
margin-bottom: 10px;
/* padding 10px */
padding: 20px;
/* margin 5px */
margin: 5px;
/* black shadow */
box-shadow: 0 0 10px 0 black;
}
/* gallery image */
.gallery-image {
width: 100%;
height: auto;
transition: transform 0.3s ease-in-out;
}
/* gallery image hover */
.gallery-image:hover {
transform: scale(1.1);
}
</style>
</head>
<body>
<div class="container">
<!-- menu items -->
<div class="menu">
<div class="menu-item">
<a href="index.php">Home</a>
</div>
<div class="menu-item">
<a href="about.php">About</a>
</div>
<div class="menu-item">
<a href="contact.php">Contact</a>
</div>
<div class="menu-item">
<a href="gallery.php">Gallery</a>
</div>
<div class="menu-item">
<a href="login.php">Login</a>
</div>
</div>
<!-- main area -->
<div class="main">
<div class="gallery">
<div class="gallery-item">
<img class="gallery-image" src="img/?random">
<h4>Product 1</h4>
<p>Description</p>
<p>Price</p>
<button>Add to cart</button>
</div>
<div class="gallery-item">
<img class="gallery-image" src="img/?random">
<h4>Product 2</h4>
<p>Description</p>
<p>Price</p>
<button>Add to cart</button>
</div>
<!-- code shortened -->
</div>
</div>
</body>
</html>
这应该在桌面上的渲染如下(确切的图片可能有所不同,因为这些 URL 生成随机的图片):
图 7.4:电子商务产品列表页面
然而,尝试在移动设备上渲染相同的页面,它看起来如下:
图 7.5:电子商务产品列表,在移动设备上看起来很糟糕
为了解决这个问题,我们需要将自己放在 CSS 代码中,并询问我们的 AI 助手我们应该做什么。
在 CSS 底部放置一个提示,如下所示:
[提示]
在移动设备上从 3 列切换到 1 列以用于画廊。
[结束提示]
结果应该是一个如下的媒体查询:
[提示响应]
@media (max-width: 768px) {
.gallery {
grid-template-columns: auto;
}
}
[结束响应]
我们新的移动渲染现在看起来像下面的图片,这是可接受的。
图 7.6:这显示了在移动设备纵向模式下的图片库渲染
作业
作为一名新聘的前端开发者,你被雇佣来维护一个记忆游戏。
游戏看起来像下面的图片:
图 7.7:记忆游戏中的网格
你的公司希望你做以下事情:
-
确保在桌面上的渲染是一个 5x5 的网格。对于更大的视口,它表现不佳,但你应该解决这个问题。
-
支持移动设备,这意味着它应该渲染为一个 5x5 的网格,但使用大小减半的瓷砖。
-
在为移动设备修复时,确保右上角的分数移动到中间并居中。
作为一名开发者,现在你的任务是使用 GitHub Copilot 调整这个游戏的代码,可以使用内联编辑开放文本文件或 Copilot 中的聊天功能来确保代码对不同设备都有效。
解决方案
你可以在 GitHub 仓库中找到这个任务的解决方案:github.com/PacktPublishing/AI-Assisted-Software-Development-with-GitHub-Copilot-and-ChatGPT
挑战
所有任务的代码都在一个文件中。看看你是否能将其拆分成不同的文件。此外,尝试进行匹配卡片的实验;尝试移除它们或添加一个显示它们不再是游戏一部分的类。
摘要
在本章中,我们讨论了视口作为响应式网页设计的核心概念。为了帮助我们处理不同的视口,我们使用了媒体查询。
我们也继续我们的用例,即电子商务网站的工作,并尝试确保产品列表在移动设备上渲染良好。首先,要意识到你有一个问题,我们已经设法识别出这个问题。其次,我们提出了一个解决问题的策略,即使用媒体查询。第三,我们实施了这一策略。最后,我们测试了它以确保它工作正常。
在下一章中,我们将从前端转向后端。后端由一个 Web API 组成。我们将继续使用我们的用例,即电子商务网站,并构建一个主要服务于产品列表的 Web API。然而,希望它也能清楚地展示如何将其他资源添加到 Web API 中。
加入我们的 Discord 社区
加入我们的社区 Discord 空间,与作者和其他读者进行讨论:
第八章:使用 Web API 构建后端
简介
当我们说 Web API 时,它是指我们开发的应用程序编程接口,旨在供客户端使用。该 API 使用 HTTP 进行通信。浏览器可以使用 Web API 向其他浏览器和应用程序公开数据和功能。
在开发 Web API 时,您可以使用您想要的任何编程语言和框架。无论选择哪种技术,都有一些您始终需要考虑的事情,比如数据存储、安全、身份验证、授权、文档、测试等等。
正是基于对需要考虑的事物的这种理解,我们可以使用 AI 助手帮助我们构建后端。
在本章中,我们将:
-
了解 Web APIs
-
使用 Python 和 Flask 创建 Web API
-
使用我们的 AI 助手回答问题、建议代码以及创建文档和测试
业务领域:电子商务
我们将在本章继续我们的电子商务示例。这次,重点是 API。API 允许您读取和写入电子商务领域中的重要数据。在开发此 API 时,您需要记住的是,它有几个重要的方面:
-
逻辑领域:将您的应用程序划分为不同的逻辑领域是有益的。在电子商务的背景下,这通常意味着产品、订单、发票等等。
-
哪个业务部分应该处理每个逻辑领域?
-
产品:可能有一个专门的团队。同一个团队管理所有类型的折扣和可能发生的活动是很常见的。
-
发票和支付:通常有一个专门的团队负责用户如何支付,例如,通过信用卡、发票和其他方式。
-
库存:您需要有一定的商品库存。您如何知道有多少?您需要与业务分析师或数据专家合作,做出正确的预测。
-
问题与数据领域
我们已经提到了关于产品、订单、发票等的一些不同的逻辑领域。在这个领域,您会遇到的问题通常是:
-
读取和写入:您希望读取或写入哪些数据(或者可能两者都要)?
-
用户将如何访问您的数据(全部数据还是可能应用过滤器以限制输出)?
-
访问和角色:您可以预期不同的角色将需要访问您的系统。管理员角色可能需要访问大部分数据,而登录用户只能看到属于他们的数据部分。这不是本章要解决的问题,但在构建此 API 时,您应该考虑这一点。
功能分解
现在我们了解到既有业务问题也有数据问题,我们需要开始识别我们需要的功能。一旦达到这个细节水平,提出具体的提示应该会更容易。
进行这种功能分解的一种方法如下——例如,对于产品:
-
阅读所有产品。
-
根据过滤器阅读产品:通常,你不会想阅读所有产品,但可能只想阅读某一类别的所有产品,或者甚至限制为特定的值,例如 10 个产品或 20 个产品。
-
搜索产品:你应该支持用户通过类别、名称或可能是某个特定活动的部分来寻找特定的产品。
-
获取特定产品的详细信息。
我相信产品还有更多功能,但现在你有了在继续构建 API 之前应该具备的详细程度的概念。
提示策略
在本章中,你将看到我们如何使用 Copilot Chat 和编辑器内模式。我们将从 Chat 模式开始,因为它在需要生成起始代码的情况下非常有用。它也非常高效,因为它允许你选择某些代码行,并基于提示仅更新这些行。后者的例子可能是当你想改进这样的代码时。你将在本章后面改进读取数据库而不是从列表中读取静态数据的路由时看到这个用例。在本章中,我们还将使用编辑器内模式。当你正在积极编写代码并想进行小的调整时,这是推荐的方法。在本章中,我们将使用第二章中描述的“探索性提示模式”。
Web API
使用 Web API 是确保我们的前端应用程序能够访问它读取和写入数据所需的数据和功能的一种很好的方式。
Web API 的期望是:
-
它可以通过网络访问。
-
它利用 HTTP 协议和 HTTP 动词,如
GET、POST、PUT、DELETE以及其他,来传达意图。
你应该选择什么语言和框架?
在本章中,我们已经决定将使用 Python 和 Flask。但为什么?我们使用什么标准来选择语言和框架?
你可以使用任何你想要的编程语言和框架,但以下是一些需要考虑的标准:
-
你知道哪些语言和框架?
-
它们是否容易学习?
-
它们是否有庞大的社区?
-
它们是免费和开源的吗?
-
它们多久更新一次?
-
它们是否有良好的文档?
-
它们是否有良好的工具?
这些只是需要考虑的一些标准。
选择 Python 和 Flask 的原因是它们符合许多上述条件(Node.js 的 Express 框架也是如此,但这里的目的是仅展示如何使用 AI 助手构建 Web API,所以请随意使用你喜欢的任何 Web 框架)。此外,本书的目的是展示 AI 助手如何帮助我们构建后端;使用什么提示、如何以及框架和语言不是重点。
规划 Web API
当你规划 Web API 时,你应该考虑以下因素:
-
你想公开哪些数据?例如,产品和订单。
-
你想公开哪些功能?例如,读取订单数据。
-
你将如何构建你的 Web API?
-
安全性和身份验证:你需要确定不仅哪些应用区域需要用户登录,还需要哪些部分应该限制给特定用户类型。
-
存储和数据库:常见的选项,例如,MySQL 和 Postgres。
当你计划你的 Web API 时,请使用上述点作为清单。
使用 Python 和 Flask 创建 Web API
与人工智能助手合作的一个关键见解是,我们可以用它来生成代码,但是我们需要对问题域和解决方案域有良好的理解。这意味着在我们请求人工智能助手帮助我们之前,我们应该知道如何使用 Python 和 Flask 创建一个 Web API。没有人工智能助手我们能创建它吗?是的,但我们可能会陷入困境,不知道下一步该做什么。
那么,我们谈论的是多少知识呢?如果你对 Python 有一般了解,并且知道如何用任何语言构建 Web API,你就准备好了。
让我们开始。
第一步:创建一个新项目
首先,我们需要创建一个新项目。如果你知道 Python,你就知道使用虚拟环境是一个好主意,因为它可以将项目与计算机上其他 Python 项目隔离开来。使用虚拟环境不是必需的,但推荐这样做,因为不同的 Python 版本和包可能会引起问题。
好的,所以我们知道我们需要一个虚拟环境。我们该如何创建一个呢?让我们依靠我们的人工智能助手来帮助我们记住语法。对于下面的提示,你可以打开一个文本文件或者使用 GitHub Copilot 中的聊天功能。
[提示]
我如何为 Python 创建虚拟环境?
[提示结束]
在编辑器中输入提示时,在打开的文本文件中,你更喜欢在句子的开头使用 q:。如果你使用聊天功能,则不需要添加 q:。
[提示响应]
答案就在 a: 同一行。
python -m venv venv
[响应结束]
下面是如何在一个包含提示和响应的 app.py 文件中查看输出:
# q: How do I create a virtual environment for Python? a: python –m venv venv
要使用人工智能助手给出的命令,我们需要打开一个终端,粘贴命令并运行它。
到这一点,你需要足够的概念知识来知道你需要激活虚拟环境才能使用它。但再次,让我们请求人工智能助手帮助我们。
[提示]
我如何在 Windows 中激活 Python 的虚拟环境?
[提示结束]
[快速响应]
a: Venv\Scripts\activate
[响应结束]
如果你使用的是 Mac 或 Linux,将上述提示更改为反映你的操作系统。
第二步:安装 Flask
你需要具备的概念知识是,你可以使用 pip 安装包——在这个例子中,是 Flask。让我们请求人工智能助手帮助我们。继续使用你的 app.py 文件,并将你的提示作为另一条注释添加。
[提示]
我如何使用 pip 安装 Flask?
[提示结束]
[快速响应]
pip install Flask
[响应结束]
你的 app.py 现在应该看起来像这样:
# q: How do I create a virtual environment for Python? a: python –m venv venv
# q: How do I install Flask with pip? a: pip install Flask
当你使用你的人工智能工具来快速获取如何做某事的答案时,是否清除文本文件中的问题和响应取决于你。
太好了,我们现在知道如何安装 Flask。
第 3 步:创建入口点
我们需要为我们的 Web API 创建一个入口点。这个文件将在我们运行 Web API 时被执行。对于 Python,你通常将这个文件命名为 app.py。
第 4 步:创建 Flask 应用
到目前为止,我们有了我们的项目,我们已经安装了 Flask,并且有一个入口点文件,但该应用还不是 Flask 应用,所以让我们创建一个。
让我们请 AI 助手帮助我们。
[提示]
我该如何创建 Flask 应用?
[提示结束]
如往常一样,在您的开放文本文件中,以 q: 开头的注释中输入您的问题。以下是您最新提示的响应:
[提示响应]
from flask import Flask
[响应结束]
我们从 AI 助手那里得到的反馈不多。但我们知道我们需要导入 Flask。让我们改进提示。
[提示]
我该如何创建一个具有默认路由和“hello”路由的 Flask 应用?
[提示结束]
现在,您将看到改进后的提示响应。提示包含更多明确的指令来创建应用中的路由和逻辑区域:
[提示响应]
from flask import Flask
# create app
app = Flask(__name__)
# default route
@app.route('/')
def index():
return 'Hello World!'
# hello route
@app.route('/hello')
def hello():
return 'Hello World!'
# start app on port 5000
if __name__ == '__main__':
app.run(debug=True, port=5000)
[响应结束]
现在我们已经有了我们的 Flask 应用的起始代码。
接下来,让我们专注于我们的 Web API 的用例——电子商务。
用例:电子商务网站的 Web API
到目前为止,在这本书中,我们带您经历了为电子商务网站构建前端应用的过程。我们使用 AI 助手帮助构建前端应用。现在,我们将为电子商务网站构建 Web API。
在上一节中,我们使用 AI 助手帮助我们创建 Flask 应用。现在,我们将使用 AI 助手帮助我们创建电子商务网站的 Web API。
第 1 步:为电子商务网站创建一个 Web API
让我们先规划这个任务。根据前面的章节,电子商务网站有以下功能:
-
产品目录:包含我们打算出售的产品。
-
购物车:有了购物车,我们可以添加产品到购物车,从购物车中移除产品,并更新购物车中产品的数量。一旦我们对购物车中的产品满意,我们就可以结账,并支付和运输产品。
-
结账流程:在结账过程中,我们需要收集支付信息和运输信息。
考虑到以上信息,让我们为我们的 AI 助手制定一个提示。
[提示]
请给我一些代码示例,用于创建一个 Flask Web API,其中包括产品目录、购物车和结账流程的路由。
[提示结束]
[提示响应]
from flask import Flask
# create app
app = Flask(__name__)
# default route
@app.route('/')
def index():
return 'Hello World!'
# product catalog
@app.route('/products')
def products():
return 'Products'
# shopping cart
@app.route('/cart')
def cart():
return 'Cart'
# checkout
@app.route('/checkout')
def checkout():
return 'Checkout'
# start app on port 5000
if __name__ == '__main__':
app.run(debug=True, port=5000)
[响应结束]
好的,所以我们已经有了我们的 Web API 的起始代码,但我们需要以下方面进行改进:
-
我们需要返回 JSON 而不是文本。
-
我们希望添加代码以读取和写入数据库。
-
我们希望添加测试。
-
我们希望记录 Web API。
第 2 步:返回 JSON 而不是文本
JSON 是一种常见的数据格式。它是一种既易于人类阅读和编写,也易于机器阅读和编写的数据格式。要在 Python 中使用 JSON,我们需要导入json包。我们将使用的方法是jsonify(),这是一个将 Python 字典转换为 JSON 的方法。
让我们看看我们如何在我们的 Web API 中使用jsonify()。对于这个操作,我们可以继续使用 AI 助手作为代码中的提示注释,或者我们可以使用 GitHub Copilot 的聊天功能(这是 GitHub Copilot 的一个独立扩展)。
让我们使用后者。要使用 GitHub Copilot 的聊天功能(有关更新的安装说明,请参阅此链接:docs.github.com/en/copilot/github-copilot-chat/using-github-copilot-chat-in-your-ide),我们需要选择我们想要改进的代码,并点击 Visual Studio Code 左侧的聊天图标(如果您使用的是默认方向,其中图标是垂直放置的——这取决于您安装的其他扩展)。这是我们要找的图标。确保 GitHub Copilot Chat 扩展已正确安装:
图 8.1:GitHub Copilot 的聊天图标
一旦我们点击聊天图标,并选择代码,我们就可以开始与 GitHub Copilot 聊天。让我们让 GitHub Copilot 帮助我们返回 JSON 而不是文本。
我们在聊天窗口中添加以下提示:
[提示]
为所有路由添加响应 JSON 的代码。
[提示结束]
图 8.2:Copilot 聊天窗口中的提示
聊天功能生成一个包含jsonify()方法的代码答案。
图 8.3:Copilot 聊天回复
这是 GitHub Copilot 生成的代码:
from flask import Flask, jsonify
# create app
app = Flask(__name__)
# default route
@app.route('/')
def index():
return jsonify({'message': 'Hello World!'})
# product catalog
@app.route('/products')
def products():
products = [
{'id': 1, 'name': 'Product 1', 'price': 10.99},
{'id': 2, 'name': 'Product 2', 'price': 5.99},
{'id': 3, 'name': 'Product 3', 'price': 7.99}
]
return jsonify(products)
# shopping cart
@app.route('/cart')
def cart():
return jsonify({'message': 'Cart'})
# checkout
@app.route('/checkout')
def checkout():
return jsonify({'message': 'Checkout'})
# start app on port 5000
if __name__ == '__main__':
app.run(debug=True, port=5000)
第 3 步:添加代码以读取和写入数据库
好的,所以我们有一个返回 JSON 的 Web API。现在,我们需要添加代码以读取和写入数据库。让我们再次使用聊天功能来帮助我们完成这个任务。
[提示]
添加代码以读取和写入数据库,并将其应用于所有我们的路由。
[提示结束]
这是我们的最终代码:
[提示回复]
import sqlite3
from flask import Flask, jsonify
# create app
app = Flask(__name__)
# connect to database
conn = sqlite3.connect('example.db')
# create table
conn.execute('''CREATE TABLE IF NOT EXISTS products
(id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL);''')
# insert data
conn.execute("INSERT INTO products (name, price) VALUES (?, ?)", ('Product 1', 10.99))
conn.execute("INSERT INTO products (name, price) VALUES (?, ?)", ('Product 2', 5.99))
conn.execute("INSERT INTO products (name, price) VALUES (?, ?)", ('Product 3', 7.99))
conn.commit()
# default route
@app.route('/')
def index():
return jsonify({'message': 'Hello World!'})
# product catalog
@app.route('/products')
def products():
cursor = conn.execute("SELECT id, name, price FROM products")
products = []
for row in cursor:
product = {'id': row[0], 'name': row[1], 'price': row[2]}
products.append(product)
return jsonify(products)
# shopping cart
@app.route('/cart')
def cart():
return jsonify({'message': 'Cart'})
# checkout
@app.route('/checkout')
def checkout():
return jsonify({'message': 'Checkout'})
# close connection
conn.close()
# start app on port 5000
if __name__ == '__main__':
app.run(debug=True, port=5000)
[回复结束]
如您所见,我们得到了相当多的代码:
-
连接到数据库的代码——在这种情况下,我们得到了连接到常见 SQLite 数据库的连接。
-
在
/products路由中创建表并从中读取的代码,但我们没有获取到写入数据库的代码。 -
我们也没有获取到读取和写入其他路由的代码。
根据上面的代码,我们意识到这是一个好的开始,但我们还有一些想要改进的地方,所以我们将更改提示以解决这些不足。我们需要确保所有路由都从数据库中读取数据,而不仅仅是其中一个。
因此,我们既可以自己调整代码,因为我们知道大部分代码已经就位,也可以尝试调整提示。让我们尝试后者。
[提示]
添加读取和写入数据库的代码,并将其应用于所有路由。
[提示结束]
[提示响应]
import sqlite3
from flask import Flask, jsonify
# create app
app = Flask(__name__)
# connect to database
conn = sqlite3.connect('example.db')
# create table
conn.execute('''CREATE TABLE IF NOT EXISTS products
(id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL);''')
# insert data
conn.execute("INSERT INTO products (name, price) VALUES (?, ?)", ('Product 1', 10.99))
conn.execute("INSERT INTO products (name, price) VALUES (?, ?)", ('Product 2', 5.99))
conn.execute("INSERT INTO products (name, price) VALUES (?, ?)", ('Product 3', 7.99))
conn.commit()
# default route
@app.route('/')
def index():
return jsonify({'message': 'Hello World!'})
# product catalog
@app.route('/products')
def products():
cursor = conn.execute("SELECT id, name, price FROM products")
products = []
for row in cursor:
product = {'id': row[0], 'name': row[1], 'price': row[2]}
products.append(product)
return jsonify(products)
# shopping cart
@app.route('/cart')
def cart():
cursor = conn.execute("SELECT id, name, price FROM products")
products = []
for row in cursor:
product = {'id': row[0], 'name': row[1], 'price': row[2]}
products.append(product)
return jsonify(products)
# checkout
@app.route('/checkout')
def checkout():
cursor = conn.execute("SELECT id, name, price FROM products")
products = []
for row in cursor:
product = {'id': row[0], 'name': row[1], 'price': row[2]}
products.append(product)
return jsonify(products)
# close connection
conn.close()
# start app on port 5000
if __name__ == '__main__':
app.run(debug=True, port=5000)
[响应结束]
改进的提示带来了以下变化:
-
已将数据库代码添加到
/checkout和/cart路由。 -
所有三个路由都从产品表中读取,这是不正确的。这是我们将在下一个提示中解决的问题。好消息是,我们只需通过提示告诉 Copilot Chat 问题所在,就可以期待 AI 助手相应地更改代码。
上述代码比我们最初的提示要好,但仍存在问题。
你可以在 Copilot Chat 中尝试调整提示,但让我们退回到我们的另一种方法——即在代码中使用提示。要添加这些提示,将光标放在代码中,并将提示作为注释添加。以下是我们添加的一些提示:
-
# 创建表:我们重复了已经使用的注释,但由于 Copilot 现在意识到了所有路由,这次它做得更好,建议创建三个数据库表而不是只有一个
products表。 -
# 添加产品到购物车,在 body 中包含 productId 和 quantity,如果需要则创建购物车,否则更新:这个提示很详细,因为它询问应该将哪些信息添加到每个购物车条目中。
-
# 产品目录 和 # 购物车 也是重复的提示(我们移除旧的提示,并重新输入以切换新的建议)。这次,我们让 Copilot 建议正确的数据库表来读取。
注意以下如何通过代码中的各种提示(作为注释)来重塑代码:
import sqlite3
import json
import flask
# create app
app = flask.Flask(__name__)
# connect to database
db = sqlite3.connect('example.db')
# create tables
db.execute('''CREATE TABLE IF NOT EXISTS products
(id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL);''')
db.execute('''CREATE TABLE IF NOT EXISTS cart
(id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL);''')
db.execute('''CREATE TABLE IF NOT EXISTS checkout
(id INTEGER PRIMARY KEY AUTOINCREMENT,
cart_id INTEGER NOT NULL,
total REAL NOT NULL);''')
db.commit()
db.close()
# default route
@app.route('/')
def index():
return flask.jsonify({'message': 'Hello World!'})
# product catalog
@app.route('/products')
def products():
db = sqlite3.connect('example.db')
cursor = db.execute("SELECT id, name, price FROM products")
products = []
for row in cursor:
product = {'id': row[0], 'name': row[1], 'price': row[2]}
products.append(product)
db.close()
return flask.jsonify(products)
# shopping cart
@app.route('/cart')
def cart():
db = sqlite3.connect('example.db')
cursor = db.execute("SELECT id, product_id, quantity FROM cart")
cart = []
for row in cursor:
item = {'id': row[0], 'product_id': row[1], 'quantity': row[2]}
cart.append(item)
db.close()
return flask.jsonify(cart)
# add product to cart, productId and quantity in body, create cart if needed, else update
@app.route('/cart/', methods=['POST'])
def add_to_cart():
db = sqlite3.connect('example.db')
# get product id and quantity from body
product_id = flask.request.json['productId']
quantity = flask.request.json['quantity']
# check if cart exists
cursor = db.execute("SELECT id FROM cart")
cart_id = None
for row in cursor:
cart_id = row[0]
# if cart exists, update
if cart_id:
db.execute("UPDATE cart SET product_id = ?, quantity = ? WHERE id = ?", (product_id, quantity, cart_id))
# else create
else:
db.execute("INSERT INTO cart (product_id, quantity) VALUES (?, ?)", (product_id, quantity))
# close
db.commit()
db.close()
return flask.jsonify({'message': 'Added to cart'})
# checkout POST, cartId in body
@app.route('/checkout', methods=['POST'])
def checkout():
# insert cart into checkout
conn = sqlite3.connect('example.db')
# get cart id from body
cart_id = flask.request.json['cartId']
# write to checkout
conn.execute("INSERT INTO checkout (cart_id, total) VALUES (?, ?)", (cart_id, 0))
# close
conn.commit()
conn.close()
这段代码足够好吗?或者我们需要进一步调整?
肯定有改进的空间,以下是你应该寻找的内容:
-
代码不是 DRY(不要重复自己);我们有大量的重复代码。我们可以通过创建一个函数来解决,该函数接受一个查询并返回结果。
-
缺少身份验证和授权。我们应该将其添加到代码中。
-
缺少文档。
-
代码不安全。我们应该添加一些安全措施,尤其是在数据库方面。作为开发者,我们需要了解如何确保代码安全,我们可以使用预处理语句来防止 SQL 注入攻击,并验证我们从客户端接收的数据。
第 4 步:改进代码
提高代码的最佳方式是以我们已有的代码作为起点,首先尝试运行它。然后我们可以看到我们得到了什么错误以及我们需要修复什么。
之后,我们专注于架构和设计,并将代码分离到不同的文件中。
最后,我们添加身份验证、授权和安全措施。
运行代码
让我们运行代码。我们需要将 Flask 变量FLASK_APP设置为app.py然后运行应用程序。
如果你使用 Windows,你需要使用set而不是export来设置变量。
# flask variable windows
set FLASK_APP=app.py
flask run
应用程序正在运行!我们现在可以访问http://localhost:5000并看到消息Hello World!。
导航到/products、/cart和/checkout也正常,但我们数据库中还没有任何数据。让我们向数据库添加一些数据。通过在文件顶部插入上述代码并再次运行应用程序,我们可以这样做:
db.execute("INSERT INTO products (name, price) VALUES (?, ?)", ('Apple', 1.99))
db.execute("INSERT INTO products (name, price) VALUES (?, ?)", ('Banana', 2.99))
现在,如果你重新运行应用程序并转到/products,你应该看到我们添加的产品,如下面的 JSON 输出所示:
[
{
"id": 1,
"name": "Apple",
"price": 1.99
},
{
"id": 2,
"name": "Banana",
"price": 2.99
}
]
重构代码
太棒了 - 到目前为止进展顺利!现在让我们重构代码,使其更加 DRY(不要重复自己,这是一个确保我们不过度重复代码的术语)。我们可以使用 Copilot 来帮助我们完成这项工作。
提供以下提示:
[提示]
通过将数据库部分分离到单独的模块db.py中并暴露以下函数来改进代码:get_products,get_cart,add_to_cart,checkout。
[提示结束]
我们得到了两个响应,一个是针对db.py文件的,另一个是针对app.py文件的。让我们从db.py文件开始。
[提示响应]
import sqlite3
# connect to database
conn = sqlite3.connect('example.db')
# create tables
conn.execute('''CREATE TABLE IF NOT EXISTS products
(id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL);''')
conn.execute('''CREATE TABLE IF NOT EXISTS cart
(id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL);''')
conn.execute('''CREATE TABLE IF NOT EXISTS checkout
(id INTEGER PRIMARY KEY AUTOINCREMENT,
cart_id INTEGER NOT NULL,
total REAL NOT NULL);''')
conn.execute("INSERT INTO products (name, price) VALUES (?, ?)", ('Apple', 1.99))
conn.execute("INSERT INTO products (name, price) VALUES (?, ?)", ('Banana', 2.99))
conn.commit()
def get_products():
cursor = conn.execute("SELECT id, name, price FROM products")
products = []
for row in cursor:
product = {'id': row[0], 'name': row[1], 'price': row[2]}
products.append(product)
return products
def get_cart():
cursor = conn.execute("SELECT id, product_id, quantity FROM cart")
cart = []
for row in cursor:
item = {'id': row[0], 'product_id': row[1], 'quantity': row[2]}
cart.append(item)
return cart
def add_to_cart(product_id, quantity):
# check if cart exists
cursor = conn.execute("SELECT id FROM cart")
cart_id = None
for row in cursor:
cart_id = row[0]
# if cart exists, update
if cart_id:
conn.execute("UPDATE cart SET product_id = ?, quantity = ? WHERE id = ?", (product_id, quantity, cart_id))
# else create
else:
conn.execute("INSERT INTO cart (product_id, quantity) VALUES (?, ?)", (product_id, quantity))
conn.commit()
def checkout(cart_id):
# insert cart into checkout
conn.execute("INSERT INTO checkout (cart_id, total) VALUES (?, ?)", (cart_id, 0))
conn.commit()
[响应结束]
我们需要稍微改进上面的代码,因为它正在使用一个打开的数据库连接。我们应该在完成操作后关闭连接。确保你在提交后添加关闭连接的代码。还要确保每个路由都打开和关闭连接。
这看起来不错。让我们将其添加到我们的项目中。我们可以创建一个名为db.py的新文件并将代码复制到其中。
接下来,让我们看看它对app.py文件的建议:
import flask
import db
# create app
app = flask.Flask(__name__)
# default route
@app.route('/')
def index():
return flask.jsonify({'message': 'Hello World!'})
# product catalog
@app.route('/products')
def products():
products = db.get_products()
return flask.jsonify(products)
# shopping cart
@app.route('/cart')
def cart():
cart = db.get_cart()
return flask.jsonify(cart)
# add product to cart, productId and quantity in body, create cart if needed, else update
@app.route('/cart', methods=['POST'])
def add_to_cart():
# get product id and quantity from body
product_id = flask.request.json['productId']
quantity = flask.request.json['quantity']
db.add_to_cart(product_id, quantity)
return flask.jsonify({'message': 'Added to cart'})
# checkout POST, cartId in body
@app.route('/checkout', methods=['POST'])
def checkout():
# get cart id from body
cart_id = flask.request.json['cartId']
db.checkout(cart_id)
return flask.jsonify({'message': 'Checkout successful'})
# start app on port 5000
if __name__ == '__main__':
app.run(debug=True, port=5000)
如你所见,它已经将数据库代码分离到db.py文件中,并且还添加了对db.py文件的导入。让我们将此代码复制到我们的app.py文件中,并替换其内容。
它工作了吗?让我们运行应用程序并看看。它没有错误地运行,当我们转到/products、/cart和/checkout时,我们得到了 JSON 输出。如果你想的话,你还可以向/cart发送一个POST请求来写入购物车内容 - 太棒了!
第 5 步:记录 API
我们可以使用 Copilot Chat 来帮助我们记录 API。让我们提供以下提示:
[提示]
使用 Swagger 记录 API。API 应该有以下端点:GET /products,GET /cart,POST /cart/add,POST /checkout。
[提示结束]
Swagger 或 OpenAPI 标准是记录你的 API 路由的一种方式。这些路由规范可以被可视化,你可以快速了解你的 API 能做什么。此外,还有一个叫做 Swagger UI 的网页,它不仅让你可以看到你的 API 中有哪些路由,你还可以进行请求等操作,以确保 API 按预期工作。
这是输出,它涉及代码和文档:
[提示响应]
# app.py
import flask
import db
from flasgger import Swagger, swag_from
# create app
app = flask.Flask(__name__)
swagger = Swagger(app)
# default route
@app.route('/')
def index():
return flask.jsonify({'message': 'Hello World!'})
# product catalog
@app.route('/products', methods=['GET'])
@swag_from('swagger/products.yml')
def products():
products = db.get_products()
return flask.jsonify(products)
# shopping cart
@app.route('/cart', methods=['GET'])
@swag_from('swagger/cart.yml')
def cart():
cart = db.get_cart()
return flask.jsonify(cart)
# add product to cart, productId and quantity in body, create cart if needed, else update
@app.route('/cart/add', methods=['POST'])
@swag_from('swagger/add_to_cart.yml')
def add_to_cart():
# get product id and quantity from body
product_id = flask.request.json['productId']
quantity = flask.request.json['quantity']
db.add_to_cart(product_id, quantity)
return flask.jsonify({'message': 'Added to cart'})
# checkout POST, cartId in body
@app.route('/checkout', methods=['POST'])
@swag_from('swagger/checkout.yml')
def checkout():
# get cart id from body
cart_id = flask.request.json['cartId']
db.checkout(cart_id)
return flask.jsonify({'message': 'Checkout successful'})
# start app on port 5000
if __name__ == '__main__':
app.run(debug=True, port=5000)
[响应结束]
它告诉我们创建以下文件和文件夹:
folder: swagger
products.yml
file: cart.yml
file: add_to_cart.yml
file: checkout.yml
file: swagger.yml
让我们手动创建这些文件并将内容复制到它们中。
products.yml
---
tags:
- products
responses:
200:
description: A list of products
schema:
type: array
items:
type: object
properties:
id:
type: integer
name:
type: string
price:
type: number
cart.yml
---
tags:
- cart
responses:
200:
description: A list of items in the cart
schema:
type: array
items:
type: object
properties:
id:
type: integer
product_id:
type: integer
quantity:
type: integer
add_to_cart.yml
---
tags:
- cart
parameters:
- in: body
name: body
required: true
schema:
type: object
properties:
productId:
type: integer
quantity:
type: integer
responses:
200:
description: Added to cart
checkout.yml
---
tags:
- checkout
parameters:
- in: body
name: body
required: true
schema:
type: object
properties:
cartId:
type: integer
responses:
200:
description: Checkout successful
缺少的是安装flassger Swagger 库:
pip install flasgger
导航到http://localhost:5000/apidocs/,你应该能看到 Swagger UI。
图 8.4:由 Swagger 生成的 API 文档
你应该通过与生成的文档交互来验证 API 是否按预期工作,并确保路由生成预期的输出。
在这个阶段,我们确实有可能继续改进,但请花点时间意识到我们仅通过提示和几行代码就创造了多少东西。我们有一个带有数据库和文档的工作 API。现在我们可以专注于改进代码和添加更多功能。
作业
这是本章的建议作业:一个好的作业是向 API 添加更多功能,例如:
-
添加一个新端点来获取单个产品。
-
添加一个新端点来从购物车中删除产品。
-
添加一个新端点来更新购物车中产品的数量。
你可以通过将上述内容添加到 Copilot Chat 作为提示并查看它生成的结果来解决这个问题。预期代码和文档都会有所变化。
解决方案
你可以在 GitHub 仓库中找到这个作业的解决方案:github.com/PacktPublishing/AI-Assisted-Software-Development-with-GitHub-Copilot-and-ChatGPT/tree/main/08
挑战
通过添加更多功能来改进这个 API。你可以使用 Copilot Chat 来帮助你完成这项工作。
摘要
在本章中,我们讨论了如何规划我们的 API。然后我们探讨了如何选择 Python 和 Flask 来完成这项工作,但强调了在实际构建 Web API 方面拥有上下文知识的重要性。一般来说,在请求 AI 助手帮助你之前,你应该至少在高级别上知道如何做某事。
然后,我们最终为 AI 助手制作了提示,以帮助我们处理 Web API。我们与我们的电子商务网站合作,创建了一个 Web API 来提供服务。
之后,我们讨论了如何改进代码并为 API 添加更多功能。
在下一章中,我们将讨论如何通过添加人工智能来改进我们的应用程序。
加入我们的 Discord 社区
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
第九章:通过人工智能服务增强 Web 应用
简介
网络应用可以通过多种方式增强人工智能服务:你可以利用现有的暴露模型的 Web API,或者自己构建并使其调用模型。
你最初想要将人工智能添加到你的应用中的原因是为了让它变得更智能。不是为了它自己的目的而变得聪明,而是为了让它对用户更有用。例如,如果你有一个允许用户搜索产品的网络应用,你可以添加一个基于用户先前购买的产品推荐功能。实际上,为什么只限于先前购买的产品呢?为什么不能根据用户的先前搜索推荐产品?或者,如果用户可以拍照产品,应用会推荐类似的产品呢?
正如你所见,有很多可能性可以增强你的网络应用,使其通过人工智能提升用户体验。
在本章中,我们将:
-
讨论不同的模型格式,如 Pickle 和 ONNX
-
学习如何使用 Python 中的 Pickle 和 ONNX 将你的模型持久化为文件
-
消费存储在 ONNX 格式的模型并通过 JavaScript REST API 公开
商业领域,电子商务
我们一直在努力工作在我们的电子商务领域,但我们的业务重点是评分。好的或坏的评分可以影响特定产品的销量。逻辑领域包括以下内容:
-
产品:要评分的产品
-
评分:实际的评分和元信息,如评论、日期等
问题和数据领域
需要解决的问题是如何使用这些评分数据并从中学习。
-
洞察:例如,我们可以得到这样的洞察,我们应该开始/停止销售某种产品。可能还有其他洞察,因为某些产品在世界某些地区销售得很好。
-
技术 问题:这一方面的技术问题是弄清楚如何摄取数据,从数据中训练模型,然后弄清楚如何让网络应用利用该模型。
特征分解
从功能的角度来看,我们需要将其视为由三个主要部分组成。
-
数据摄取和训练:这需要一个单独的界面,可能是在没有用户界面的情况下完成的,只是静态数据被输入到能够从数据中训练模型的代码中。有了这个理解,我们可以概述以下步骤:
-
加载数据
-
清洗数据
-
创建功能
-
训练模型
-
评估模型
-
运行预测
-
-
消费模型:一旦模型训练完成,它需要被公开,最好是通过 Web 端点。为了达到这一点,我们认为我们需要以下步骤:
-
如有必要,将模型转换为合适的格式
-
构建 Web API
-
通过 Web API 公开模型
-
部署模型,这里有一个步骤需要将 API 上线
-
-
预测:对于预测部分,这是一个面向“后台”而非面向客户的功能。
-
构建用户界面以运行预测
-
构建与 Web API 通信的底层代码以实现预测
-
提示策略
你可以选择在这里采用提示方法,要么从提示注释中获取建议,要么使用聊天界面。
对于提示模式,我们将使用第二章中描述的“探索模式”。
创建模型
假设我们正在处理sales_rating.csv文件中的以下数据,这是合并两个数据集的结果,一个包含销售数据,另一个包含评分数据。数据看起来像这样:
product_id,avg_rating,sold
1,2.5,100
2,3.7,200
3,4.2,300
4,1.3,50
5,4.9,800
6,3.2,150
7,2.1,80
8,4.8,500
9,3.9,400
10,2.4,200
11,4.1,300
12,3.2,100
13,2.9,150
14,4.5,500
15,3.8,400
16,2.7,200
17,4.3,300
18,3.4,100
19,2.3,150
20,4.7,500
前面的数据显示了一个包含product_id、avg_rating和sold列的数据集。我们的理论是产品的平均评分与销售数量相关。一个高评分的产品似乎会比一个低评分的产品卖得更好,这是一个合理的假设。通过创建模型,我们可以更接近地确定我们的假设是否成立。
制定计划
我们可以向 Copilot 询问创建模型所需的步骤。我们将这些提示作为注释输入:
如果你想让 Copilot 提供关于代码之外的其他文本答案,你可以在代码文件或 Markdown 文件中的注释中输入文本。在注释之后放置下一行,并从“a:”开始行,也有助于。你也可以使用 Copilot Chat 提问:
[提示]
告诉我生命、宇宙和万物的答案。
[结束提示]
[提示响应]
a: 42
[结束响应]
上述参考来自道格拉斯·亚当斯的书籍《银河系漫游指南》。
接下来,让我们让 Copilot 帮助我们了解创建模型的步骤。
[提示]
在给定的 CSV 文件中创建模型有哪些步骤?
[结束提示]
输出:
-
导入 pandas 和 scikit-learn
-
读取 CSV 文件
-
创建测试和训练数据集
-
创建一个模型
-
模型的效果如何?
-
预测
-
将模型保存为 pkl
好的,我们有一个计划,那么就执行它,并使用 Copilot 帮助我们编写代码。
导入库
首先,让我们在 VS Code 中创建一个新的笔记本。让我们称它为sales.ipynb。
.ipynb是 Jupyter Notebook 的文件格式,作为数据科学实践者,你很可能熟悉它。想法是你在文件中工作,文件由单元格组成,其中一些是文本单元格,其他是你可以运行的代码单元格。
要使用某些库,我们需要安装它们。我们可以使用 Copilot 来帮助我们。我们可以利用我们是数据科学实践者的这一事实,因此了解我们需要哪些库。因此,我们将建议以下 pandas 和 scikit-learn 库来帮助我们完成任务。
[提示]
在笔记本中安装 pandas 和 scikit-learn。
[结束提示]
[提示响应]
! pip install pandas scikit-learn
[结束响应]
接下来,让我们导入所需的库。
[提示]
在笔记本中导入 pandas 和 scikit-learn。
[结束提示]
[提示响应]
# Import pandas and scikit-learn in a notebook.
import pandas as pd
import sklearn.linear_model
[结束响应]
读取 CSV 文件
现在我们有了需要的库,让我们读取 CSV 文件。
[提示]
在笔记本中读取 CSV 文件。
[提示结束]
[提示回复]
# Read a CSV file in a notebook.
df = pd.read_csv("sales_rating.csv")
[回复结束]
CSV 文件现在已加载到 pandas DataFrame 中,这是一个包含列和行的结构。让我们看看前几行。
df.head()
这将产生以下输出:
product_id avg_rating sold
0 1 2.5 100
1 2 3.7 200
2 3 4.2 300
3 4 1.3 50
4 5 4.9 800
太好了,我们已经将数据加载到 DataFrame 中。接下来,我们需要将数据分为测试集和训练集。
创建测试集和训练集
在机器学习中,我们通常将数据分为测试集和训练集。训练集用于训练模型,测试集用于测试模型。我们可以使用 Copilot 来帮助我们完成这项工作。
[提示]
在笔记本中创建测试集和训练集,建议测试集大小为 0.2。
[提示结束]
[提示回复]
# Create a test and training dataset in a notebook, suggested test size of 0.2
test_size = 0.2
test = df.sample(frac=test_size)
training = df.drop(test.index)
[回复结束]
0.2 是一个好的测试集大小。这意味着 20%的数据将用于测试,80%的数据将用于训练。
您可能需要在相应的行中开始键入“test_size”和“training”,然后按TAB键接受建议。
现在我们已经设置好了,接下来让我们创建一个模型。
创建模型
创建模型涉及选择我们想要使用的算法,然后训练模型。让我们提出一个提示:
[提示]
在笔记本中使用LinearRegression算法创建一个模型。
[提示结束]
[提示回复]
# Create a model in a notebook using the LinearRegression algorithm.
model = sklearn.linear_model.LinearRegression()
model.fit(training[["avg_rating"]], training[["sold"]])
[回复结束]
在前面的代码中,我们使用LinearRegression算法创建了一个模型,然后使用fit方法使用训练集训练了模型。同时,请注意我们是如何使用avg_rating列作为输入,sold列作为输出的。这是因为我们想要根据平均评分预测销售数量。
模型有多好?
在这个阶段,我们想知道模型有多好。为了找出这一点,让我们请 Copilot 来帮助我们。
您可以使用注释风格并获取内联建议,或者使用 Copilot Chat。在两种情况下,Copilot 都会根据上下文理解。
[提示]
在笔记本中,模型表现如何?显示代码。
[提示结束]
[提示回复]
# How good is the model in a notebook? Show the code.
score = model.score(test[["avg_rating"]], test[["sold"]])
[回复结束]
调用模型上的score并传入test数据集似乎是我们了解模型有多好的方法。我们甚至可以询问 Copilot 关于这一点:
[提示]
分数是什么意思?
[提示结束]
[提示回复]
score returns the coefficient of determination R² of the prediction.
[回复结束]
如果您愿意,您可以继续查询并更详细地解释,如下面的提示:
[提示]
这在简单术语中意味着什么?
[提示结束]
[提示回复]
it returns a number between 0 and 1, where 1 is perfect prediction and 0 is no prediction.
[回复结束]
预测
在我们尝试预测某些东西之前,我们不会知道模型有多好,即使我们可以参考分数值。让我们为这个任务编写一个提示:
[提示]
在笔记本中预测。显示代码。
[提示结束]
[提示回复]
# Predict in a notebook. Show the code.
predictions = model.predict(test[["avg_rating"]])
[回复结束]
好的,我们得到了一个名为 predictions 的变量,它包含预测值,但很难理解这些预测是否合理。让我们尝试询问 Copilot 并看看它有什么建议。
[提示]
你建议我们如何在笔记本中显示预测结果?
[提示结束]
[提示响应]
print the predictions
[响应结束]
[提示]
除了打印之外,你建议我们还有哪些方法可以在笔记本中显示预测结果?
[提示结束]
[提示响应]
plot the predictions
[响应结束]
现在我们已经获得了关于我们各种选项的更多信息,让我们利用这些知识在提示中使用:
[提示]
在笔记本中展示如何打印和绘制预测数据与实际数据对比的图表。
[提示结束]
[提示响应]
# Show me how to print and plot predictions vs actual data in a notebook.
import matplotlib.pyplot as plt
plt.scatter(test[["avg_rating"]], test[["sold"]], color='black')
plt.plot(test[["avg_rating"]], predictions, color='blue', linewidth=3)
[响应结束]
在笔记本中运行此代码将生成以下图表:
图 9.1:显示预测数据与实际数据对比的图表
在图表中,看起来这条线很好地拟合了数据。我们还可以打印预测值以查看实际值:
print(predictions)
将模型保存到 .pkl 文件
我们还剩下一步:我们需要将模型保存到文件中。我们有几种不同的保存模型的方式。例如,我们可以将其保存为 pickle 文件或 ONNX 文件。让我们先从将其保存为 pickle 文件开始。
如同往常,让我们为这个任务创建一个提示:
[提示]
在笔记本中将模型保存为 pkl。
[提示结束]
[提示响应]
# Save the model to pkl in a notebook.
import pickle
pickle.dump(model, open("model.pkl", "wb"))
[响应结束]
为了完成上述内容,你可能需要开始输入“import”以获取建议。另外,它很可能使用 pickle 库,所以你可以在下一行开始输入“pickle”并按 TAB 键接受建议。
在 Python 中创建 REST API
到目前为止,我们有一个包含我们模型的 model.pkl 文件。我们可以通过这个文件通过 REST API 暴露模型。让我们让 Copilot 帮助我们完成这个任务。
[提示]
在 Python 中创建一个 REST API,以暴露 pkl 文件中的模型。
[提示结束]
[提示响应]
# Create a REST API in Python that exposes the model in a pkl file.
import pickle
import flask
# load the model
model = pickle.load(open("model.pkl", "rb"))
# app
app = flask.Flask(__name__)
# routes
@app.route('/', methods=['GET', 'POST'])
def predict():
if flask.request.method == 'GET':
return "Please POST a JSON object to this endpoint to get a prediction."
else:
data = flask.request.json
prediction = model.predict([[data["avg_rating"]]])
return flask.jsonify({"prediction": prediction[0][0]})
[响应结束]
注意我们上面使用了好几个提示。首先,我们在文件顶部添加了提示“创建一个在 Python 中暴露 pkl 文件的 REST API”,从而设置了上下文。然后我们使用各种辅助提示,如“加载模型”、“应用”和“路由”,帮助我们编写代码。最终我们得到了一个通过 REST API 暴露模型的 API。
将模型转换为 ONNX
到目前为止,你已经看到了我们如何使用 Pickle 保存模型,然后在 Python 中加载它。然而,Pickle 有一些缺点,包括它是 Python 特定的。另一方面,ONNX 是一种不是 Python 特定的格式,可以在其他语言中使用。让我们看看我们如何使用 ONNX 格式来处理我们的模型。
我们需要分两步解决我们的任务:
-
将模型转换为 ONNX 格式。
-
加载模型并在 JavaScript 中创建一个 REST API。
在 ONNX 格式中创建模型
因为我们使用的是笔记本,我们需要安装依赖项,导入我们需要的库,然后将模型转换为 ONNX 格式。
[提示]
我需要哪些 ONNX 依赖项?展示安装这些依赖项的 Python 代码。
[提示结束]
[提示回复]
! pip install skl2onnx
[回复结束]
[提示]
展示如何导入我需要的 ONNX 库,并展示如何将模型转换为 ONNX 格式。
[提示结束]
[提示回复]
import skl2onnx
# convert the model to ONNX
skl2onnx.to_onnx(model, train[["avg_rating"]].astype("float32"), "model.onnx")
[回复结束]
现在我应该有一个包含 ONNX 格式模型的 model.onnx 文件。让我们看看我们是否可以在 JavaScript 中加载它。
在 JavaScript 中加载 ONNX 模型
接下来,我们需要在 JavaScript 中(特别是 Node.js,因为我们是在后端进行此操作)加载 ONNX 模型。我们可以使用 onnxruntime 库来完成此操作。让我们让 Copilot 帮助我们。
[提示]
展示一个逐步指南,说明如何在 JavaScript 中加载 ONNX 模型。
[提示结束]
[提示回复]
-
在 JavaScript 中安装 onnxruntime
-
在 JavaScript 中加载 ONNX 模型
-
使用 ONNX 模型在 JavaScript 中进行预测
[回复结束]
现在我们有了计划,让我们执行它。
在 JavaScript 中安装 onnxruntime
因为我们正在构建一个 Node.js 项目,所以首先,在一个新文件夹中创建一个 app.js 文件,并运行 npm init -y 终端命令来创建一个新的 Node.js 项目。
我们计划的第一个步骤是在 JavaScript 中安装 onnxruntime。我们可以使用 npm 来执行此任务。
[提示]
在 JavaScript 中安装 onnxruntime。
[提示结束]
[提示回复]
npm install onnxruntime
[回复结束]
很好,这已经在我们的项目中安装了 onnxruntime 库。接下来,我们需要加载 ONNX 模型。
在 JavaScript 中加载 ONNX 模型
现在我们已经安装了 onnxruntime,我们可以加载 ONNX 模型。
[提示]
在 JavaScript 中加载 ONNX 模型。
[提示结束]
[提示回复]
const rt = require("onnxruntime");
# load model from ONNX
model rt.InferenceSession("model.onnx");
# predict
model.run(null, {model.get_inputs()[0].name: [[4.5]]});
[回复结束]
在前面的代码中,我们从 ONNX 文件中加载模型,然后使用模型对输入 4.5 进行预测,以表示平均评分,看看我们可以期待多少销售额。
任务:使用 JavaScript 构建一个消耗模型的 REST API
-
将我们在上一节中创建的模型添加到笔记本中,将其转换为 ONNX 文件。
-
在存储库中创建一个名为的新文件夹,并在该文件夹中创建一个名为
app.js的新文件。 -
将 POST /predict 路由添加到
server.js文件中,并确保它根据输入返回一个预测结果。
这里有一些你可以尝试的起始提示,以帮助你完成这个任务:
-
提示:使用 Express 在 JavaScript 中创建 REST API
-
提示:使用 Express 在 JavaScript 中创建一个 POST /predict 路由
-
提示:使用 Express 在 JavaScript 中从 ONNX 加载模型
-
提示:使用 Express 在 JavaScript 中的 REST API 中使用 ONNX 模型进行预测
解决方案
请参阅代码库 [github.com/PacktPublishing/AI-Assisted-Software-Development-with-GitHub-Copilot-and-ChatGPT/tree/main/09] 和 09 文件夹以获取解决方案。
测验
Pickle 和 ONNX 之间的区别是什么?
-
Pickle 是 Python 特定的,而 ONNX 则不是。
-
Pickle 可以在 JavaScript 中使用,而 ONNX 则不能。
-
ONNX 的效率不如 Pickle。
摘要
在本章中,我们介绍了各种模型格式,如 Pickle 和 ONNX,以及如何使用 Python 将模型持久化为文件。将模型存储为文件很有用,因为它允许您将其与其他应用程序集成。
然后,我们讨论了存储模型的不同格式的优缺点,如 Pickle 和 ONNX。我们得出结论,ONNX 可能是更好的选择,因为它不是 Python 特定的,并且可以在其他语言中使用。
然后,我们介绍了如何使用 JavaScript 加载存储为 ONNX 格式的模型,并创建 REST API 使模型可供其他应用程序使用。
在下一章中,我们将更详细地介绍如何使用 GitHub Copilot 并充分利用它。我们将涵盖技巧和窍门以及有助于让您更快、更高效的功能。
加入我们的 Discord 社区
加入我们社区的 Discord 空间,与作者和其他读者进行讨论:
第十章:维护现有代码库
简介
棕色地带是另一种与现有代码一起工作的说法。在我的开发生涯中,大部分工作都是在现有代码上完成的。棕色地带的对立面是绿色地带,这是一个没有现有代码的新项目。
因此,了解如何与现有代码库一起工作非常重要,并且在棕色地带环境中与像 GitHub Copilot 这样的 AI 助手一起工作时,有很多令人兴奋的事情。
在本章中,我们将:
-
了解不同类型的维护。
-
了解我们如何通过流程进行维护以降低引入更改的风险。
-
使用 GitHub Copilot 来帮助我们进行维护。
提示策略
本章与其他章节的书籍略有不同。重点是描述您可能在现有代码库空间中遇到的各种问题。建议您使用最舒适的提示建议方法,无论是提示注释还是聊天界面。至于模式,鼓励您尝试所有三种主要模式,即第二章中描述的 PIC、TAG 或探索性模式。然而,本章的重点是使用“探索性模式”。
不同类型的维护
有不同类型的维护,了解它们之间的区别非常重要。以下是一些您可能会遇到的不同类型:
-
纠正性维护:这是我们修复错误的时候。
-
适应性维护:在这种情况下,我们更改代码以适应新的需求。
-
改进性维护:当我们不改变功能的情况下改进代码。这可能是重构或提高代码性能的例子。
-
预防性维护:更改代码以防止未来的错误或问题。
维护流程
每次您更改代码时,都会引入风险。例如,一个错误修复可能会引入新的错误。为了减轻这种风险,我们需要遵循一个流程。一个建议的流程可能是以下步骤:
-
识别:识别问题或需要做出的更改。
-
检查:检查测试覆盖率以及您的代码被测试覆盖的情况。覆盖得越好,您发现引入的任何错误或其他问题的可能性就越大。
-
计划:计划更改。您将如何进行?您将编写哪些测试?您将运行哪些测试?
-
实施:实施更改。
-
验证:验证更改是否按预期工作。运行测试,运行应用程序,检查日志等。
-
集成:这是确保您在分支中做出的任何更改都能合并到主分支中。
-
发布/部署更改:您希望确保最终客户能够利用这次更改带来的好处。为了实现这一点,您需要部署它。
我们是否需要为每次更改都涵盖所有这些步骤?不,这取决于更改;一些更改可能非常小,我们可能希望在发布我们希望最终客户拥有的版本之前将它们捆绑在一起。好消息是,我们有一个在 GitHub Copilot 中的优秀工具可以帮助我们处理这些小步骤,使我们能够专注于更重要步骤。
解决错误
有些错误比其他错误更严重。在最佳情况下,你的错误很少遇到,或者即使发生,也不是什么大问题。在最坏的情况下,你的错误会让你损失金钱。让我们关注一个在运行电子商务网站的场景中,错误让你损失金钱的情况。
希望这段代码从未在生产环境中发布过,但它是一个对电子商务网站可能造成灾难性后果的错误的好例子:
def process_cart():
# get cart from database
cart = get_cart()
card = cart.get_card()
process_payment(card, cart)
ship_order(cart)
你可能遇到的问题是,即使process_payment失败,也会调用ship_order,这意味着你发货了但没有收到付款,损失了金钱。
看看这段代码,如果你在process_payment抛出错误而你甚至没有到达ship_order,你可能不会有问题。
如果它没有,而是返回一个依赖于支付结果的数值代码,会怎样?
def process_payment(card, cart):
if card.is_valid():
if card.has_funds(cart.total):
charge_card(card, cart.total)
return 0
else:
return 1
如果你没有读取错误代码,你可能不会注意到支付失败,订单已经发货。那么,我们的方法是什么?
1. 识别问题
首先,我们需要识别问题;在这种情况下,你可以做两件事,即:
-
阅读代码:我们的代码看起来是什么样子?是否抛出错误,返回一个代码,或者完全是其他东西?
-
检查测试:我们是否有覆盖这个场景的测试?如果没有,我们需要编写一个。这个测试应该显示我们有一个错误。
让我们使用 Copilot 来完成这个任务,使用如下提示(这里,我们模拟process_payment返回错误代码而不是抛出错误)。在主窗口中选择ship_order代码,然后在聊天窗口中输入以下提示:
[提示]
为代码编写一个测试;如果process_payment返回 1,则不应调用ship_order。
[提示结束]
我们正在使用 GitHub Copilot 聊天功能,并在我们的文本编辑器中选择process_cart代码。
你应该看到类似于以下代码的响应:
[提示响应]
```python
import unittest
from unittest.mock import Mock, patch
from your_module import process_cart # 替换为实际的模块名称
class TestProcessCart(unittest.TestCase):
@patch('your_module.get_cart') # 替换为实际的模块名称
@patch('your_module.process_payment')
@patch('your_module.ship_order')
def test_order_not_shipped_if_payment_fails(self, mock_ship_order, mock_process_payment, mock_get_cart):
# 安排
mock_cart = Mock()
mock_cart.get_card.return_value = 'test_card'
mock_get_cart.return_value = mock_cart
mock_process_payment.return_value = 1
# 行动
process_cart()
# 断言
mock_ship_order.assert_not_called()
if __name__ == '__main__':
unittest.main()
```py
[响应结束]
上述代码包含一个测试,测试如果从process_payment得到错误代码 1 会发生什么。我们希望发生的事情是ship_order不被调用。
这个测试将失败,因为我们的代码中有一个错误,这是好事,因为我们已经识别出一个错误,并且可以通过测试证明它的存在。
2. 实施更改
现在我们有一个显示我们存在错误的测试,我们可以实施更改。我们可以自信地做出代码更改,因为我们有一个显示我们存在错误的测试,并且我们希望测试覆盖率也得到了足够的提高。
除非你有良好的测试覆盖率,或者变化很小,你确信不会引入错误,否则不要开始重构。
我们现在如何改进代码?简单——我们可以使用 GitHub Copilot 帮助我们实现。让我们使用以下提示来帮助我们进行所需的代码更改:
[提示]
实施更改;如果process_payment返回 1,则不应调用ship_order。
[提示结束]
你为process_cart编写的代码现在应该看起来像这样:
[快速响应]
```python
def process_cart():
# 从数据库获取购物车
cart = get_cart()
card = cart.get_card()
payment_status = process_payment(card, cart)
# 只有在支付成功时才发货
if payment_status != 1:
ship_order(cart)
```py
[响应结束]
上述代码是一个正确的步骤,因为它现在包含了一个检查支付状态的if语句。
为了进一步改进代码,我们可以使用 Copilot 帮助我们使代码更健壮、更易读。让我们使用以下提示进行额外的改进:
[提示]
重构代码,使其更容易维护。
[提示结束]
这可能会给你以下代码:
[提示响应]
def get_payment_status(cart):
card = cart.get_card()
return process_payment(card, cart)
def process_cart():
# get cart from database
cart = get_cart()
payment_status = get_payment_status(cart)
# Only ship the order if the payment was successful
if payment_status != 1:
ship_order(cart)
[响应结束]
在这里,Copilot 建议我们将支付状态提取到一个单独的函数中。这是一个好建议,因为它使代码更容易阅读和维护。
添加新功能
在这个例子中,我们将查看现有的代码库并添加一个新功能。代码库类似于我们之前使用的例子,但它更复杂一些。这是因为它涉及更多涉及不同类型支付方式的函数。以下是代码:
```python
import re
def validate_card(card):
# 信用卡以 4 开头,有 13 或 16 位数字。
# 信用卡以 51 至 55 开头,有 16 位数字。
visa_pattern = r'⁴[0-9]{12}(?:[0-9]{3})?$'
mastercard_pattern = r'⁵[1-5][0-9]{14}$'
if re.match(visa_pattern, card):
return 'Visa'
elif re.match(mastercard_pattern, card):
return 'MasterCard'
else:
return None
def process_payment(card, cart):
card_type = validate_card(card)
if card_type is None:
return 1 # 无效卡
else:
# 在这里处理支付
# 如果支付成功,则返回 0,否则返回 1
pass
def process_cart():
# 从数据库获取购物车
cart = get_cart()
card = cart.get_card()
payment_status = process_payment(card, cart)
# 只有在支付成功时才发货
if payment_status == 0:
ship_order(cart)
```py
在上述代码中,我们有以下内容:
-
validate_card验证卡片号码,如果有效则返回卡片类型;否则返回None。 -
process_payment处理支付,如果支付成功则返回0;否则返回1。 -
process_cart处理购物车,获取卡片,处理支付,如果支付成功,则发货。
1. 识别问题并找到需要更改的函数/函数
我们的任务是使我们的代码也支持美国运通卡。实现新的卡片意味着我们需要更新 validate_card 函数以支持美国运通卡。
现在,我们知道哪些函数需要更改,但 GitHub Copilot 知道吗?
让我们用提示来询问 GitHub Copilot。
在识别步骤中,我们利用我们的专业知识来识别需要更改的内容和位置。一个好的做法是依靠你自己的技能和 Copilot 工具的组合来帮助分析和识别问题。
2. 实施更改,并添加新功能和测试
让我们使用 GitHub Copilot 来帮助我们。我们可以使用以下提示:
[提示]
添加对美国运通卡的支持。
[提示结束]
你将得到类似以下的代码:
[快速响应]
import re
def validate_card(card):
# Visa cards start with 4 and have 13 or 16 digits.
# MasterCard cards start with 51 through 55 and have 16 digits.
# American Express cards start with 34 or 37 and have 15 digits.
visa_pattern = r'⁴[0-9]{12}(?:[0-9]{3})?$'
mastercard_pattern = r'⁵[1-5][0-9]{14}$'
amex_pattern = r'³[47][0-9]{13}$'
if re.match(visa_pattern, card):
return 'Visa'
elif re.match(mastercard_pattern, card):
return 'MasterCard'
elif re.match(amex_pattern, card):
return 'American Express'
else:
return None
[响应结束]
在上述代码中,Copilot 做了以下更改:
-
在
validate_card函数中创建了一个新的正则表达式以支持美国运通卡。 -
添加了一个新的
elif语句,如果卡片与新的正则表达式匹配,则返回美国运通。
当然,我们还没有完成;我们需要添加一个测试来验证代码是否按预期工作。让我们使用以下提示:
[提示]
编写一个测试用例;validate_card 应该对有效卡返回“美国运通”。
[提示结束]
你的测试应该看起来像下面这样:
[提示响应]
```python
import unittest
from your_module import validate_card # 用实际的模块名替换
class TestValidateCard(unittest.TestCase):
def test_validate_card_returns_amex_for_valid_amex_card(self):
# 安排
valid_amex_card = '378282246310005'
# 行动
result = validate_card(valid_amex_card)
# 断言
self.assertEqual(result, '美国运通')
if __name__ == '__main__':
unittest.main()
```py
[响应结束]
新的测试用例 test_validate_card_returns_amex_for_valid_amex_card 由 Copilot 添加,并测试 validate_card 函数对有效的美国运通卡返回 American Express。
在这一点上,继续迭代是个好主意;你不仅想测试代码对有效卡片的处理,还要测试无效卡片等。
你可以用 Copilot 作为挑战来编写更多的测试。
提高性能
提高性能是一个常见的任务,但可能很难实现。通常,你需要特定的工具来衡量性能,并且你需要知道要衡量什么。了解如何以这种方式改进你的代码的一个好方法是使用装饰器来 分析 方法执行所需的时间。然后,你可以衡量特定的方法,找出哪些方法需要改进。
这里是一个在睡眠 2 秒的函数上使用装饰器的例子:
import time
def profile(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__} took {end - start} seconds')
return result
return wrapper
@profile
def sleep_two_seconds():
time.sleep(2)
sleep_two_seconds()
profile 函数是一个装饰器,它接受一个函数作为输入,并返回一个包装输入函数的函数。wrapper 函数测量输入函数的执行时间,并打印结果。
如果你通过终端运行此代码,你会看到如下类似的输出:
sleep_two_seconds took 2.000000238418579 seconds
好的,那么我们如何利用这个来提高性能呢?让我们使用 GitHub Copilot,看看我们如何获得关于改进代码性能的建议。
这是我们要改进的代码。它处理记录并查找重复项:
def process_records(records):
for i in range(len(records)):
for j in range(len(records)):
if i != j and records[i] == records[j]:
print(f'Duplicate record found: {records[i]}')
大多数看到这个的开发者会回想起他们的算法和数据结构书籍,并开始思考像 大 O 符号 这样的事情,这是一种衡量算法性能的方法。主要关注的是减少昂贵的操作次数以提高性能,同时保持相同的功能。
所以,我们应该向 GitHub Copilot 提出的问题是如何改进这段代码?让我们使用以下提示:
[提示]
提高代码的性能。
[提示结束]
我们得到如下反馈:
[快速响应]
def process_records(records):
record_set = set()
for record in records:
if record in record_set:
print(f'Duplicate record found: {record}')
else:
record_set.add(record)
[响应结束]
那么,为什么这段代码更好呢?让我们来证明它:
-
首先,通过计算原始代码和新代码的大 O 符号。
-
第二,通过使用我们之前创建的装饰器。
大 O 符号计算
尝试这个提示:
[提示]
这段代码的大 O 复杂度是多少?也请展示你是如何计算它的。
[提示结束]
你应该得到如下反馈:
[快速响应]
提供的代码的时间复杂度是 O(n²),其中 n 是记录数。
这是它的计算方式:
-
外循环运行 n 次,其中 n 是记录数。这是因为它遍历列表中的每个记录。
-
在外循环内部,还有一个内循环,由于同样的原因,它也会运行 n 次。
-
由于内循环在每次外循环迭代时都会运行 n 次,所以总的迭代次数是 n * n,即 n²。
因此,时间复杂度是 O(n²)。
这意味着如果你将记录数加倍,处理这些记录所需的时间将增加大约四倍。这是二次时间复杂度的特征。
[响应结束]
好的,O(n²) 不是很好,但新代码怎么样呢?
在新代码上运行相同的提示:
[快速响应]
提供的代码的时间复杂度是 O(n),其中 n 是记录数。
这是它的计算方式:
-
循环运行 n 次,其中 n 是记录的数量。这是因为它遍历列表中的每个记录。
-
在循环内部,操作(检查记录是否在集合中以及可能将其添加到集合中)是常数时间,O(1),因为在 Python 中,集合操作通常平均是常数时间。
因此,时间复杂度是 O(n) ** O(1)*,这简化为 O(n)。
这意味着如果你将记录的数量加倍,处理这些记录所需的时间也将加倍,这是线性时间复杂度的特征。
[响应结束]
O(n) 比 O(n²) 更好,但好多少?让我们在下一节中衡量这一点。
测量性能
好的,所以从算法上讲,我们知道新代码更好,但好多少呢?让我们通过使用装饰器和调用代码来衡量。让我们使用 1,000 条记录和 10,000 条记录的输入,并比较执行代码所需的时间。
好的,让我们看看新代码是否比旧代码更快。让我们用 10,000 条记录来尝试:
# old code
@profile
def process_records(records):
for i in range(len(records)):
for j in range(len(records)):
if i != j and records[i] == records[j]:
print(f'Duplicate record found: {records[i]}')
records_10000 = [i for i in range(10000)]
process_records(records_10000)
运行这段代码,你应该看到以下输出:
process_records took 5.193912506103516 seconds
现在,让我们运行新的代码:
# new code
@profile
def process_records(records):
record_set = set()
for record in records:
if record in record_set:
print(f'Duplicate record found: {record}')
else:
record_set.add(record)
records_10000 = [i for i in range(10000)]
process_records(records_10000)
运行这段代码,你应该看到以下输出:
process_records took 0.0011200904846191406 seconds
如你所见,通过结合你的知识和 GitHub Copilot,你可以改进你的代码。
你的代码并不总是这么明显,你可能需要做更多的工作来提高性能。建议你使用性能分析器来测量性能,然后使用 GitHub Copilot 来帮助你改进代码。
提高可维护性
另一个有趣的使用案例是使用 GitHub Copilot 来帮助你提高代码的可维护性。那么,你可以做些什么来提高代码的可维护性呢?以下是一个列表:
-
改进命名变量、函数、类等。
-
分离关注点:例如,将业务逻辑与展示逻辑分开。
-
移除重复:特别是在大型代码库中,你很可能会发现重复。
-
提高可读性:例如,你可以通过使用注释、文档字符串、事件测试等方式来提高可读性。
让我们从代码库开始,看看我们如何可以改进它。以下是代码:
def calculate_total(cart, discounts):
# Define discount functions
def three_for_two(items):
total = 0
for item in items:
total += item.price * (item.quantity - item.quantity // 3)
return total
def christmas_discount(items):
total = 0
for item in items:
total += item.price * item.quantity * 0.5
return total
def cheapest_free(items):
items_sorted = sorted(items, key=lambda x: x.price)
total = 0
for item in items_sorted[1:]:
total += item.price * item.quantity
return total
# Calculate totals for each discount
totals = []
for discount in discounts:
if discount == '3 for 2':
totals.append((discount, three_for_two(cart)))
elif discount == 'Christmas Discount':
totals.append((discount, christmas_discount(cart)))
elif discount == 'Cheapest Free':
totals.append((discount, cheapest_free(cart)))
# Sort totals and return the lowest one
totals_sorted = sorted(totals, key=lambda x: x[1])
return totals_sorted[0][1] if totals_sorted else sum(item.price * item.quantity for item in cart)
# Define discounts
discounts = ['3 for 2', 'Christmas Discount', 'Cheapest Free']
# Define cart
cart = [
{'name': 'item1', 'price': 10, 'quantity': 3},
{'name': 'item2', 'price': 20, 'quantity': 1},
{'name': 'item3', 'price': 30, 'quantity': 1}
]
# Calculate total
total = calculate_total(cart, discounts)
print(f'Total: {total}')
上述代码库在高级别上描述了以下内容:
-
一个函数,
calculate_total,用于计算购物车和一系列折扣的总计 -
一系列折扣
-
一个购物车
从这个代码库中,你可以看到它不太容易维护,因为它很难理解发生了什么。
1. 识别问题。你看到了哪些问题?
让我们先问问 GitHub Copilot 对这段代码的看法,并决定下一步要做什么。让我们使用以下提示:
[提示]
你对这段代码的看法是什么?你会怎么做来改进它?
[提示结束]
你会收到一个包含项目符号列表和改进代码的详细响应。让我们先看看项目符号列表:
[快速响应]
-
使用类来表示商品和折扣:目前,商品和折扣分别用字典和字符串表示。使用类来表示这些实体会使代码更易读和灵活。
-
分离折扣函数:当前的折扣函数定义在
calculate_total函数内部。最好将这些函数单独定义,以提高可读性并使代码更容易测试和维护。 -
使用更可扩展的折扣应用方法:当前应用折扣的方法涉及一系列
if-elif语句。通过将折扣函数存储在字典或Discount对象列表中,然后遍历该集合来应用折扣,可以使这种方法更具可扩展性。
[回复结束]
上述识别出的问题都似乎是合理的——使用类、分离函数和使用字典。
2. 添加测试并降低风险
在我们查看建议的代码更改之前,我们需要最小化引入错误的风险。我们首先应该做的事情是检查我们的测试覆盖率。在这种情况下,我们没有测试,所以我们需要编写一些。让我们使用以下提示:
[提示]
为此代码编写测试,覆盖所有可能的执行路径。
[提示结束]
[提示回复]
import unittest
class TestCalculateTotal(unittest.TestCase):
def test_no_discounts(self):
cart = [
{'name': 'item1', 'price': 10, 'quantity': 3},
{'name': 'item2', 'price': 20, 'quantity': 1},
{'name': 'item3', 'price': 30, 'quantity': 1}
]
discounts = []
total = calculate_total(cart, discounts)
self.assertEqual(total, 80)
def test_three_for_two_discount(self):
cart = [
{'name': 'item1', 'price': 10, 'quantity': 3},
{'name': 'item2', 'price': 20, 'quantity': 1},
{'name': 'item3', 'price': 30, 'quantity': 1}
]
discounts = ['3 for 2']
total = calculate_total(cart, discounts)
self.assertEqual(total, 70)
def test_christmas_discount(self):
cart = [
{'name': 'item1', 'price': 10, 'quantity': 3},
{'name': 'item2', 'price': 20, 'quantity': 1},
{'name': 'item3', 'price': 30, 'quantity': 1}
]
discounts = ['Christmas Discount']
total = calculate_total(cart, discounts)
self.assertEqual(total, 40)
def test_cheapest_free_discount(self):
cart = [
{'name': 'item1', 'price': 10, 'quantity': 3},
{'name': 'item2', 'price': 20, 'quantity': 1},
{'name': 'item3', 'price': 30, 'quantity': 1}
]
discounts = ['Cheapest Free']
total = calculate_total(cart, discounts)
self.assertEqual(total, 70)
def test_multiple_discounts(self):
cart = [
{'name': 'item1', 'price': 10, 'quantity': 3},
{'name': 'item2', 'price': 20, 'quantity': 1},
{'name': 'item3', 'price': 30, 'quantity': 1}
]
discounts = ['3 for 2', 'Christmas Discount', 'Cheapest Free']
total = calculate_total(cart, discounts)
self.assertEqual(total, 40)
if __name__ == '__main__':
unittest.main()
[回复结束]
现在,你永远不应该相信 AI 生成的结果,所以请花时间审查代码,运行测试覆盖率工具,确保你在进行下一步之前对代码感到满意,下一步是改进代码。
为了改进 Copilot 的第一个建议的代码,你想要确保它能够运行,并在添加商品到购物车等操作中使用Item类。以下是这些修改的结果:
import unittest
from discount_old import calculate_total, Item
from item import Item
class TestCalculateTotal(unittest.TestCase):
def test_no_discounts(self):
cart = [
Item('item1', 10, 3),
Item('item2', 20, 1),
Item('item3', 30, 1)
]
discounts = []
total = calculate_total(cart, discounts)
self.assertEqual(total, 80)
def test_three_for_two_discount(self):
cart = [
Item('item1', 10, 3),
Item('item2', 20, 1),
Item('item3', 30, 1)
]
discounts = ['3 for 2']
total = calculate_total(cart, discounts)
self.assertEqual(total, 70)
def test_christmas_discount(self):
cart = [
Item('item1', 10, 3),
Item('item2', 20, 1),
Item('item3', 30, 1)
]
discounts = ['Christmas Discount']
total = calculate_total(cart, discounts)
self.assertEqual(total, 40)
def test_cheapest_free_discount(self):
cart = [
Item('item1', 10, 3), #30
Item('item2', 20, 1), # 20
Item('item3', 30, 1) # 30
]
discounts = ['Cheapest Free']
total = calculate_total(cart, discounts)
self.assertEqual(total, 60)
def test_multiple_discounts(self):
cart = [
Item('item1', 10, 3),
Item('item2', 20, 1),
Item('item3', 30, 1)
]
discounts = ['3 for 2', 'Christmas Discount', 'Cheapest Free']
total = calculate_total(cart, discounts)
self.assertEqual(total, 40)
if __name__ == '__main__':
unittest.main()
对于应用程序代码:
from item import Item
def calculate_total(cart, discounts):
# Define discount functions
def three_for_two(items):
total = 0
for item in items:
total += item.price * (item.quantity - item.quantity // 3)
return total
def christmas_discount(items):
total = 0
for item in items:
total += item.price * item.quantity * 0.5
return total
def cheapest_free(items):
if not items:
return 0
items_sorted = sorted(items, key=lambda x: x.price * x.quantity)
cheapest_item = items_sorted[0]
total = 0
for item in items:
total += item.price * item.quantity
total -= cheapest_item.price * cheapest_item.quantity if cheapest_item.quantity > 0 else 0
return total
# Calculate totals for each discount
totals = []
for discount in discounts:
if discount == '3 for 2':
totals.append((discount, three_for_two(cart)))
elif discount == 'Christmas Discount':
totals.append((discount, christmas_discount(cart)))
elif discount == 'Cheapest Free':
totals.append((discount, cheapest_free(cart)))
# Sort totals and return the lowest one
totals_sorted = sorted(totals, key=lambda x: x[1])
return totals_sorted[0][1] if totals_sorted else sum(item.price * item.quantity for item in cart)
# if run as script,
if __name__ == '__main__':
# Define discounts
discounts = ['3 for 2', 'Christmas Discount', 'Cheapest Free']
# Define cart
cart = [
Item('item1', 10, 3),
Item('item2', 20, 1),
Item('item3', 30, 1)
]
# Calculate total
total = calculate_total(cart, discounts)
print(f'Total: {total}')
好的,现在我们有一个我们满意的代码库,并且通过了测试。让我们现在看看 GitHub Copilot 建议的代码更改。
3. 实施更改并提高可维护性
好的,我们对我们有良好的测试覆盖率感到自信,我们准备改进代码。让我们使用以下提示:
[提示]
提高代码的可维护性。
[提示结束]
你应该得到类似以下的结果:
[提示回复]
为了提高代码的可维护性,我们可以进行以下更改:
-
将折扣函数从
calculate_total中移出,使其成为独立的函数。这提高了可读性,并使单独测试这些函数更容易。 -
使用字典将折扣名称映射到相应的函数。这消除了在应用折扣时需要多个
if/elif语句的需求。
from item import Item
# Define discount functions
def three_for_two(items):
total = 0
for item in items:
total += item.price * (item.quantity - item.quantity // 3)
return total
def christmas_discount(items):
total = 0
for item in items:
total += item.price * item.quantity * 0.5
return total
def cheapest_free(items):
if not items:
return 0
items_sorted = sorted(items, key=lambda x: x.price * x.quantity)
cheapest_item = items_sorted[0]
total = 0
for item in items:
total += item.price * item.quantity
total -= cheapest_item.price * cheapest_item.quantity if cheapest_item.quantity > 0 else 0
return total
def calculate_total(cart, discounts):
# Map discount names to functions
discount_functions = {
'3 for 2': three_for_two,
'Christmas Discount': christmas_discount,
'Cheapest Free': cheapest_free
}
# Calculate totals for each discount
totals = []
for discount in discounts:
if discount in discount_functions:
totals.append((discount, discount_functionsdiscount))
# Sort totals and return the lowest one
totals_sorted = sorted(totals, key=lambda x: x[1])
return totals_sorted[0][1] if totals_sorted else sum(item.price * item.quantity for item in cart)
# if run as script,
if __name__ == '__main__':
# Define discounts
discounts = ['3 for 2', 'Christmas Discount', 'Cheapest Free']
# Define cart
cart = [
Item('item1', 10, 3),
Item('item2', 20, 1),
Item('item3', 30, 1)
]
# Calculate total
total = calculate_total(cart, discounts)
print(f'Total: {total}')
[回复结束]
通过引入以下更改,代码现在更容易维护:
-
calculate_total函数变得大大缩小,其中它曾经包含的每个函数都已移出 -
提到的相同方法现在也使用字典将名称映射到函数
有效使用 Copilot 的关键是持续迭代,持续进行小的改动,并查看 Copilot 提出的建议。稍微改变提示,看看会发生什么,并选择最适合您需求的输出。
挑战
尝试使用提示和 GitHub Copilot 进一步改进代码。如果支付出现问题,应该怎么办?您应该记录它,也许抛出一个错误等?
更新现有的电子商务网站
让我们继续改进我们在前几章中介绍的电子商务网站。在本章中,我们将专注于改进代码库和添加新功能。
为了参考,让我们展示我们开始创建的 basket.html 文件:
<!-- a page showing a list of items in a basket, each item should have title, price, quantity, sum and buttons to increase or decrease quantity and the page should have a link to "checkout" at the bottom -->
<html>
<head>
<title>Basket</title>
<link rel="stylesheet" href="css/basket.css">
<!-- add bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<!-- add 3 basket items with each item having id, name, price, quantity, use card css class -->
<!--
<div class="container">
<div id="basket" class="basket">
</div>
</div> -->
<!-- add app.js -->
<!-- add app.js, type javascript -->
<div id="basket" class="basket">
<!-- render basket from Vue app, use Boostrap -->
<div v-for="(item, index) in basket" class="basket-item">
<div class="basket-item-text">
<h2>{{ item.name }}</h2>
<p>Price: {{ item.price }}</p>
<p>Quantity: {{ item.quantity }}</p>
<p>Sum: {{ item.price * item.quantity }}</p>
</div>
<div class="basket-item-buttons">
<button type="submit" class="btn btn-primary btn-block btn-large" @click="increaseQuantity(index)">+</button>
<button type="submit" class="btn btn-primary btn-block btn-large" @click="decreaseQuantity(index)">-</button>
</div>
</div>
</div>
<!-- add Vue 3 -->
<script src="img/vue@next"></script>
<!-- add boostrap code for Vue 3 -->
<script>
let app = Vue.createApp({
data() {
return {
basket: [
{
id: 1,
name: 'Product 1',
price: 100,
quantity: 1
},
{
id: 2,
name: 'Product 2',
price: 200,
quantity: 2
},
{
id: 3,
name: 'Product 3',
price: 300,
quantity: 3
},
{
id: 4,
name: 'Product 4',
price: 400,
quantity: 4
}
]
}
},
methods: {
increaseQuantity(index) {
this.basket[index].quantity++;
},
decreaseQuantity(index) {
if (this.basket[index].quantity > 0) {
this.basket[index].quantity--;
}
}
},
computed: {
total() {
let total = 0;
for (let i = 0; i < this.basket.length; i++) {
total += this.basket[i].price * this.basket[i].quantity;
}
return total;
}
}
});
app.mount('#basket');
</script>
<!-- <script src="img/app.js"></script> -->
</body>
这是一个原型早期版本;它不是指用于生产的代码。然而,我们可以使用此代码来改进它并使其适用于生产。
现在我们知道这段代码并不好,让我们使用 Copilot 和以下提示来改进它:
[提示]
改进代码,使其适用于生产环境。
[提示结束]
这个提示突出了代码中的一些问题,特别是:
[提示响应]
-
将 JavaScript 和 CSS 分离到外部文件:这使代码更易于维护,并允许缓存这些资源。
-
使用构建工具或模块打包器:像 Webpack 或 Parcel 这样的工具可以帮助您管理依赖项,将现代 JavaScript 转译为旧版本以获得更好的浏览器兼容性,并压缩您的代码以用于生产。
-
错误处理:向您的函数添加错误处理以确保您的应用程序可以优雅地处理意外情况。
-
Vue 组件结构:不要在单个脚本中创建整个
Vue 应用程序,而是将其分解成可重用的组件。 -
可访问性:通过使用适当的 ARIA 角色和属性确保您的 HTML 可访问。
[响应结束]
那是一个我们可以询问的很好的列表,但让我们看看它建议的代码更改。它建议将代码分成两个不同的文件,一个用于 HTML,另一个用于 JavaScript:
<!-- index.html -->
<html>
<head>
<title>Basket</title>
<link rel="stylesheet" href="css/basket.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div id="basket" class="basket"></div>
<script src="img/vue@next"></script>
<script src="img/app.js"></script>
</body>
</html>
现在,让我们看看 JavaScript 是如何得到改进的:
// app.js
let app = Vue.createApp({
data() {
return {
basket: [
{ id: 1, name: 'Product 1', price: 100, quantity: 1 },
{ id: 2, name: 'Product 2', price: 200, quantity: 2 },
{ id: 3, name: 'Product 3', price: 300, quantity: 3 },
{ id: 4, name: 'Product 4', price: 400, quantity: 4 }
]
}
},
methods: {
increaseQuantity(index) {
this.basket[index].quantity++;
},
decreaseQuantity(index) {
if (this.basket[index].quantity > 0) {
this.basket[index].quantity--;
}
}
},
computed: {
total() {
return this.basket.reduce((total, item) => total + item.price * item.quantity, 0);
}
}
});
app.mount('#basket');
这是一个巨大的进步。作为一名开发者,您可以看到 JavaScript 代码使用了静态数据,所以让我们用以下提示来改变它:
[提示]
改进代码,通过调用 API 使用动态数据,并生成一个具有路由 /api/basket 的单独 Python API,其中包含购物车数据。
[提示结束]
现在,我们有两个文件,一个是 API,另一个是 JavaScript 代码。让我们先看看 API:
[提示响应]
# app.py
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/api/basket')
def get_basket():
basket = [
{ 'id': 1, 'name': 'Product 1', 'price': 100, 'quantity': 1 },
{ 'id': 2, 'name': 'Product 2', 'price': 200, 'quantity': 2 },
{ 'id': 3, 'name': 'Product 3', 'price': 300, 'quantity': 3 },
{ 'id': 4, 'name': 'Product 4', 'price': 400, 'quantity': 4 }
]
return jsonify(basket)
if __name__ == '__main__':
app.run(debug=True)
在您上面得到一个返回购物车数据的路由 /api/basket。让我们看看改进后的 JavaScript 代码:
let app = Vue.createApp({
data() {
return {
basket: []
}
},
methods: {
increaseQuantity(index) {
this.basket[index].quantity++;
},
decreaseQuantity(index) {
if (this.basket[index].quantity > 0) {
this.basket[index].quantity--;
}
}
},
computed: {
total() {
return this.basket.reduce((total, item) => total + item.price * item.quantity, 0);
}
},
mounted() {
fetch('/api/basket')
.then(response => response.json())
.then(data => {
this.basket = data;
});
}
});
app.mount('#basket');
[响应结束]
太好了!我们现在从 API 获取数据。我们如何改进代码?如果您之前构建过 Vue 应用程序,您将知道它涉及到使用 Vue CLI 并有一个构建步骤。让我们用以下提示询问 GitHub Copilot:
[提示]
改进代码,使用 Vue CLI,并添加一个构建步骤。
[提示结束]
现在,我们以如下方式将命令和文件更改作为响应返回:
[提示响应]
在您的机器上全局安装Vue CLI:
npm install -g @vue/cli
创建一个新的Vue项目
vue create my-project
cd my-project
现在,让我们创建一个新的Vue组件用于篮子。在src/components目录中创建一个新的文件Basket.vue:
<!-- src/components/Basket.vue -->
<template>
<div id="basket">
<!-- Your basket template goes here -->
</div>
</template>
<script>
export default {
data() {
return {
basket: []
};
},
methods: {
increaseQuantity(index) {
this.basket[index].quantity++;
},
decreaseQuantity(index) {
if (this.basket[index].quantity > 0) {
this.basket[index].quantity--;
}
}
},
computed: {
total() {
return this.basket.reduce((total, item) => total + item.price * item.quantity, 0);
}
},
mounted() {
fetch('/api/basket')
.then(response => response.json())
.then(data => {
this.basket = data;
});
}
};
</script>
<style scoped>
/* Your CSS goes here */
</style>
然后,在主App.vue中导入并使用此组件:
<!-- src/App.vue -->
<template>
<div id="app">
<Basket />
</div>
</template>
<script>
import Basket from './components/Basket.vue';
export default {
name: 'App',
components: {
Basket
}
};
</script>
<style>
/* Your CSS goes here */
</style>
现在,您可以使用Vue CLI 的构建命令来创建一个生产就绪版本的应用程序:
npm run build
这将创建一个包含应用程序生产就绪版本的dist目录。
[响应结束]
这看起来像是正确的步骤。然而,您应该始终测试代码以确保它按预期工作。
作为挑战,看看上述代码是否工作,以及您是否可以进一步改进它。
代码可能在框架的不同版本之间发生变化。在这种情况下,我们使用 Vue.js,所以请确保查阅您使用的版本的文档。
作业
找到您想要改进的代码片段。按照本章概述的过程进行:
-
识别问题。您看到了什么问题?
-
添加测试,降低变更风险。
-
实施更改并提高可维护性。
如果您没有想要改进的代码片段,请尝试使用本章中的代码或 Kata(Gilded Rose)GitHub 页面上的代码:github.com/emilybache/GildedRose-Refactoring-Kata。
知识检查
- 绿色田野开发和棕色田野开发有什么区别?
A:绿色田野开发是指从头开始编码;棕色田野开发是指更新现有代码。
- 更新现有代码的最佳方式是什么?
A:最好的方法是进行小改动,并确保有足够的测试。
摘要
在本章中,我们确立了编写代码的一个重要方面是更新现有代码,这被称为棕色田野开发。我们还探讨了 GitHub Copilot 如何帮助您完成这项任务。
从本章中可以得出的最重要的信息是确保您有一个更新代码的方法,以降低即将进行的变更风险。多次进行小改动比一次性进行大改动要好。在开始更改代码之前,强烈建议您有足够的测试。
加入我们的 Discord 社区
加入我们社区的 Discord 空间,与作者和其他读者进行讨论: