在和 Copilot 一同连续写完一个约三千行代码的项目之后,我对它的脾气秉性终于有了大致的了解。这里是关于运用它的技巧,以及穿插的聊聊哪些是它能做的,善于做的,还有做不到的。

假如你问我 copilot 能够多大程度上替代程序员,我的答案是至多可认为程序员完结一半的作业。这并不是意味这公司有一半的程序员能够被开除,而是说程序员的作业能够事半功倍的完结。假如说我们都同意编程本质上和写作无异,都是一类将想法输出的进程,那 copilot 能为你做的有且只要后半部分,想法的塑造依然要依靠你自己。

阅读完以下的 copilot 的运用(结对)技巧你就会对我的上述结论有更深刻的了解。文中我会保存一切 copilot 生成的内容,以便各位对它做进一步独立评估

给予清晰的指示

你能够把 copilot 当作十分靠谱的入门程序员,它也许无法给你一些规划、架构上的主张,但它能够很好的完结你拆分给它的子使命。最常见 copilot 的运用方法就是给予它清晰且详细的使命指令,比方在注释中给出以下使命:

// Write a block of code using JS fetch API to get data from the server with these conditions:
// 1. Request method: GET
// 2. Request URL: http://localhost:3000/api/v1/contacts
// 3. Request header: Content-Type: application/json:
// 4. Convert the response to JSON format
// The code is:

随后 VSCode 便会生成代码

fetch('http://localhost:3000/api/v1/contacts', {
    method: 'GET',
    headers: {
        'Content-Type': 'application/json'
    }
})
    .then(res => res.json())
    .then(data => console.log(data))
    .catch(err => console.log(err));

这关于写一些你常用、并且大致有形象、可是又不常记于心的代码十分好用。比方虽然我常写 SQL 查询句子,可是对创立或许删去数据库操作并不了解,于是我能够直接让它告知我:

-- Write a SQL script to drop the database "example" and all tables in it:
DROP DATABASE IF EXISTS example;

指令编写的越详细越好,比方下述指令 Write a function to replace the new line and space characters with a <br /> tag. 就会比该指令 Write a function to replace the ending characters with a <br /> tag. 要好,由于你清晰指出了 ending characters 究竟是什么

把 Copilot 当作谷歌或许维基百科来用

这是一种很重要的思想模式的改变:copilot 能够带来沉浸式的编程体验,即一切的作业都能够在当时 IDE 中去完结,而不必被浏览器掠夺注意力。除了 copilot 自带的代码解释功用之外,当你遇到编程方面的问题时,能够尝试用注释的方法去问询它而不是去谷歌,比方以下是我和它的两次对话(你只要写出注释 Q 后边的问题,那么它会新起一行以 A 最初给予答复):

-- Q: MySQL database name should connect with "-" or "_"?
-- A: MySQL database name should connect with "_", because "-" is a special character in MySQL.
-- Q: In Nodejs project, the folder name should connect with "-" or "_"?
-- A: In Nodejs project, the folder name should connect with "-", because "_" is a special character in Nodejs.

我是否应该信任它的答复?答案应该是肯定的,由于反过来想,假如但凡它给出的答案你都选择进行二次确认,这对你是巨大的担负。你需求的是一次信仰之跃(Leap of faith),信任它就好

关于详细的代码答复,它会给出链接而非转移代码,比方当我问询 Generate NodeJS code for sending message to AzureServiceBus 时,它会直接给出链接 docs.microsoft.com/en-us/azure…

上下文很重要

同一条指令在不同的文件位置可能会得出不一样的代码结果,例如这是我在一个全新的 JS 文件里给出上一小节相同指令所得到的结果:

// Generate NodeJS code for sending message to AzureServiceBus:
//
// 1. Create a new NodeJS project
// 2. Add the following to the package.json file:
//    "dependencies": {
//      "azure-sb": "^0.10.3"
//    }
// 3. Run "npm install"
// 4. Copy this file to the project folder
// 5. Run "node send.js"
//
// Note: This sample uses the callback style of calling the send method.
//       There is also a promise style available.
//
// Documentation References:
// - ServiceBus NodeJS SDK API Reference
//   https://docs.microsoft.com/en-us/javascript/api/azure-sb/?view=azure-node-latest
// - ServiceBus NodeJS SDK Samples
//
// **************************************************************************************************

可是假如你把这段指令放在一个名为 sendMessageToAzureServiceBus 的函数中,它会直接优先给出代码或许上面说到的链接:

async function sendMessageToAzureServiceBus() {
    // Generate NodeJS code for sending message to AzureServiceBus:
    var azureServiceBus = require("@azure/service-bus");
    var sbClient = new azureServiceBus.ServiceBusClient("<connection-string>");
    var sender = sbClient.createSender("<queue-name>");
    var message = {
        body: "Hello world"
    };
}

由于在后者的上下文中,很明显此时此刻你更想要的是完结

命名

上下文的重要性不仅仅体现在指令地点的位置这一层面,事实上文件里的一切蛛丝马迹都能够被 copilot 感知到,恰当的函数和变量命名尤其重要。

例如你现在想规划一个表结构用于存储播客节意图单集信息,假如你将表名命名为 item 的话,那么当你输入完 CREATE TABLE IF NOT EXISTS Item 之后,copilot 补全的结果如下:

CREATE TABLE IF NOT EXISTS Item (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    description TEXT,
    price REAL NOT NULL,
    image TEXT,
    category_id INTEGER NOT NULL,
    FOREIGN KEY (category_id) REFERENCES Category(id)
);

看起来和播客关系不大是不是,但假如你一开始就把表名规划为 PodcastEpisodeItem,那么 copilot 补全结果又是另一番现象

CREATE TABLE IF NOT EXISTS PodcastEpisodeItem (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    podcastId INTEGER NOT NULL,
    title TEXT NOT NULL,
    description TEXT NOT NULL,
    pubDate TEXT NOT NULL,
    url TEXT NOT NULL,
    downloaded INTEGER NOT NULL DEFAULT 0,
    FOREIGN KEY(podcastId) REFERENCES Podcast(id)
)

表的字段规划看上去更契合播客节意图信息了。

这个技巧在编写函数时相同管用,假如你的函数名只是称为 makeSometingDouble,它生成的函数指只会是

function makeSomethingDouble(value) {
    return value * 2;
}

但假如你的意图是让数组里的每一个元素值翻倍,那么函数名最好叫 doubleArray,那么它生成的函数也会更契合你的希望

function doubleArray(array) {
  return array.map(item => item * 2);
}

文件

文件的命名也很重要,它能够给 copilot 供给满足多的提示来告知它你想干什么或许说它应该干什么,例如当你创立一个名为 azure-service-bus-service.js 的文件之后,只需求在空白文件中输入 async function s——是的你没看错,你乃至都不需求输入完好 send 这个单词,它就会为生成一个 sendMessage 的函数:

async function sendMessages() {
  const sbClient = new ServiceBusClient(connectionString);
  const sender = sbClient.createSender(queueName);
  const messages = [
    { body: "Albert Einstein" },
    { body: "Werner Heisenberg" },
    { body: "Marie Curie" },
  ];
  try {
    for (let i = 0; i < messages.length; i++) {
      const message = messages[i];
      console.log(`Sending message: ${message.body}`);
      await sender.sendMessages(message);
    }
    await sender.close();
    await sbClient.close();
  } catch (err) {
    console.log("Error occurred: ", err);
  }
}

假如你接受了这段它供给的代码,那么接下来新起一行,你无需任何输入,它即会主动提出 receiveMessages 函数:

async function receiveMessages() {
    const sbClient = new ServiceBusClient(connectionString);
    const receiver = sbClient.createReceiver(queueName);
    try {
        const messages = await receiver.receiveMessages(1);
        if (messages.length > 0) {
        console.log(`Received message: ${messages[0].body}`);
        await receiver.completeMessage(messages[0]);
        } else {
        console.log("No messages to receive");
        }
        await receiver.close();
        await sbClient.close();
    } catch (err) {
        console.log("Error occurred: ", err);
    }
    }

由于 azure-service-bus-service 这个称号现已供给满足窄的限制和丰厚的上下文,来标准文件内代码的功用。假如你的文件只是广泛的叫做 queue-service.js,那么当你告知它生成一个名为 sendMessages 的函数的时分,它给出的完结也十分广泛:

async function sendMessages() {
    const messages = await getMessages();
    const queue = await getQueue();
    await queue.sendMessages(messages);
    await deleteMessages(messages);
}

这里的 queue 能够代指不同云服务商供给的 queue 工具

打开关联文件

在编程进程中,把一切相关的文件在 IDE 中都打开对 copilot 编程了解上下文相同有帮助。比方你在编写 A 文件代码的进程中需求引用 B 文件里的函数,那么最好在 IDE 中也打开 B 文件。

抱歉我无法安稳的复现和证明这一点,在写该文章的当下我运用的是一个文件数量不多的 demo 项目,可是在编写大型项意图进程中我确实能够体会到这一点,在之后生成的代码中,它乃至能够精确生成参数

让它有的抄

copilot 十分善于参考现有项目里已有的代码。这是一件功德,一方面它能够确保代码的一致性,另一方面假如它所抄袭的代码是你通过深思熟虑手写出来的,那么出错的概率也会小一些

举个比如,假如你项目中的写出了一个函数用于向 example 域名恳求数据,其间借用的是 JS 原生 fetch 函数:

async function sendRequestToExampleAPI() {
  fetch('https://example.com/api', {
    headers: {
      'Content-Type': 'application/json'
    },
    method: 'POST',
  }).then((response) => {
    return response.json();
  }).then((data) => {
    console.log(data);
  })
}

那么下一个向 NewsAPI 恳求数据的函数,copilot 会完全参照上述函数的完结内容生成。下面代码除了函数名词以外皆由 copilot 生成:

async function sendRequestToNewsAPI {
  fetch('https://newsapi.org/v2/everything?q=bitcoin&from=2020-08-29&sortBy=publishedAt&apiKey=API_KEY', {
    headers: {
      'Content-Type': 'application/json'
    },
    method: 'GET',
  }).then((response) => {
    return response.json();
  }).then((data) => {
    console.log(data);
  })
}

函数结构,“外观”如出一辙。

你还能够这么测试它的“抄袭”功用,无妨把第一个函数的 fetch 替换成运用 axios,那么第二个函数生成时也会改用 axios 完结

async function sendRequestToNewsAPI () {
    axios.get('https://newsapi.org/v2/top-headlines?country=us&apiKey=API_KEY')
        .then((response) => {
            console.log(response);
        }, (error) => {
            console.log(error);
        });
}

注意乃至你无法阻挠它抄袭其他文件内容的,我尝试在注释里告知它 don’t refer any other code in the same space 但并不管用

给予提示

copilot 并不是完美的,有时分它供给的一切主张都不会肯定契合你的要求,这个时分就就需求你对它给予引导并且给予提示。

例如你给出了以下指令:Write a function to connect the MySQL database and run some SQL statements`,随后它生成的第一条句子是

const mysql = require('mysql');

这里可能会呈现和你预期不符的情况,例如你想运用另一个类库完结数据库操作,在这种情况下你需求对它生成的代码做一步一步的修正。

更恰当的方法其实是在文件的最初就引入特定你想运用的类库,又或许在编写完部分代码之后再对它给出指令。例如以下是我提前写出的代码:

const mysql = require('mysql2/promise')
const cfg = require('./config');
const db = mysql.createPool(cfg.mysql);

这样一来它便知道一切 SQL 都应该交由 mysql2 来完结,并且连接池现已为它准备完毕,在我给出指令 Write a async function to run SQL query 之后它生成的代码如下:

// Write a async function to run SQL query
async function query(sql, params) {
    const [rows, fields] = await db.query(sql, params);
    return rows;
    }

有时分你乃至需求从官方文档中黏贴一些代码片段到文件中用于给予它提示,或许在文件最初运用注释描述一下该文件的用途也相同有效


在 JetBrain 的 Marketplace 里我看到关于 Copilot 插件有这么一段简介:

  • 74% of developers are able to focus on more satisfying work
  • 88% feel more productive
  • 96% of developers are faster with repetitive tasks

我对其间的 repetitive tasks 深有感触,无妨回顾一下你每天的编码作业,其间有多少是重复无数次的机械劳动,有多少是听着播客或许音乐就能够完结的使命,它们都理应被 copilot 干掉。但也正如本文所示,“你”还无法被干掉,程序还需求你来主导,copilot 写出来的代码需求你来 review。

在 《Competing in the Age of AI 》一书中,公司价值被认为由两部分组成:事务模型(business model)和运营模型(operation model),事务模型决议公司的中心竞争力是什么,为用户创造了何种价值;而运营模型则决议了价值将怎么被交付到用户手中。copilot 是个别运营自我的手段,而非事务模型的体现

本文还一起发布在我的个人网站上,欢迎重视