Using AI Programming to "Enhance Development Efficiency"

Introduction

Ever since the birth of ChatGPT, I have been using web-based LLMs to assist with development. Over these past two years, I've maintained a conservative stance towards AI programming, particularly skeptical about the feasibility of AI-generated projects. Recently, I took over a frontend project and started using GitHub Copilot, and I found my attitude towards AI programming has become more complex. On one hand, the Agent mode is incredibly convenient and significantly boosts development efficiency. On the other hand, looking at the existing code of AI-generated projects, I really didn't want to modify it (although I ultimately relied on AI to refactor the entire project).

In the pre-AI programming era, code scalability and maintainability were always among the criteria for evaluating code quality. Entering the AI programming era, writing code has become easier, but the overall code quality of a project cannot achieve a comparable qualitative improvement with the help of AI. Therefore, the foundation of AI programming ultimately rests with humans. Only developers with systems thinking and engineering experience can truly achieve efficiency gains through AI programming.

In this post, I will start with various types of prompts to share some of my experiences using the VS Code Agent for programming, as well as some of my thoughts on AI programming.

Prompts

As an LLM product, AI programming naturally relies on prompts. Here, prompts are not limited to the text we type into a chat box; they also include:

  • Predefined prompts, such as the .copilot-instructions.md file mentioned later;
  • Various documents, like requirement documents. Of course, they can also be AI-generated;
  • Various data, such as accessing data from a database via MCP;

Predefined prompts and AI-generated documents can be seen as an application of Chain-of-Thought (CoT), aimed at helping the LLM think better.

Prompts Influence Code Quality

Why emphasize the often-discussed concept of prompts first? Because prompts largely determine the quality of the generated code. Taking JS as an example, when an LLM generates a piece of code, it often uses the chained processing of the Promise API instead of the modern async/await syntax. Note, which syntax is used is a matter of probability, depending on the data the LLM was trained on. The JS code that can be scraped from the internet certainly uses the Promise API more frequently, so the LLM is trained to prefer this approach.

Using different syntax is a minor issue because it just doesn't align with our preferred coding standards; we can manually change it or ask the LLM to rewrite it using another syntax. However, for the same reason, the quality of the generated code is often not great. For popular languages online, like Python and JS, there are undoubtedly far more junior developers than senior developers. Therefore, there is far more mediocre code than above-average code. The LLM is trained to be more inclined to produce code at this level.— I'm not saying my code level is high, because I'm also not accustomed to the style prevalent in high-quality code, like the extensive use of functions found in the Linux kernel source code. This reminds me of what a content creator said: "Object-oriented programming might be something we need to learn for a lifetime." The principles in software engineering textbooks are only truly mastered by us through practice.

Of course, the high probability of an LLM writing mediocre code doesn't mean it cannot write high-quality code. We just need to guide it through prompts. The reader might wonder: If I don't even know how to write it well myself, how can I guide it? The answer is simple: use software development principles as prompts, such as "Keep functions short and single-responsibility; extract reusable logic into pure functions or utility functions."

Using Reasonable Prompts

Let's see how to reasonably utilize various types of prompts to make AI generate code quickly and well.

Chat

When I want to implement a feature, I first ask myself: Can I implement this feature completely without relying on AI? In other words, do I know what needs to be done at each step?

If I don't know, then I first research possible implementation approaches through search or a web-based LLM, comparing the pros and cons of various schemes until I roughly understand what needs to be done at each step.

Then, I write the prompt like this:

1
2
3
4
5
6
Let's develop a feature like this, ...

- Step 1
- Step 2
- Step 3
- ...

Clear step-by-step instructions ensure that the code written by the AI is controllable and understandable by us. Why do this? Although VS Code can easily undo changes (the restore checkpoint feature), reading useless code wastes our time and energy, and generating this code wastes tokens. Therefore, it's better to do a bit more work before letting the AI write the code, achieving twice the result with half the effort. Of course, whether we can provide these steps depends on our development experience. If we can't, we can also save the implementation plan provided by the web-based LLM into a document, then include this document in the chat and write "You need to refer to the implementation in this document."

Predefined Prompts

It's impractical to copy large chunks of software development principles and our own development preferences into every chat session. Predefined prompts solve this problem. In VS Code, by creating a .github/copilot-instructions.md file and filling it with content, this file is automatically included in chats, serving as a predefined prompt.

The format of this file is usually as follows.

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
## Project Overview

The business scenario of this project is ...

## Tech Stack

- Programming Language
- Framework
- Toolkits
- ...

## Directory Structure & Naming

Main directories include:

- Subdirectory1: Description
- Subdirectory2: Description
- ...

## Coding Standards

- ...

## Framework Best Practices

- ...

## Toolkit Best Practices

- ...

The content inside can be generated by AI. However, since the LLM will generate code based on the content in this file, we must first understand its content, know which parts we prefer and which we dislike, and make reasonable choices, which also requires our development experience. Similarly, we can write our additional requirements in it.

Note, if this file is written unreasonably, it might have the opposite effect, resulting in generated code that is worse than the default generation.

Documents

Attaching documents during chat is a common operation; various web-based LLMs have long implemented this feature, so I won't elaborate much here. What I want to say is that the waterfall model, which we no longer use, seems to have been reborn with the power of LLMs. The benefits of iterative development, besides adapting to changing requirements, also include sparing us the work of writing various design documents and drawing various diagrams (sequence diagrams, class diagrams, state diagrams, etc.). This work is certainly useful, but it's too time-consuming and energy-draining, unsuitable for the pace of Web development.

Now, with LLMs, we can have them sequentially generate documents for requirements, database tables (classes and their relationships), component interaction logic, API interfaces, etc. These documents can both guide our work and give the LLM direction when developing specific features.

For example, we can generate a requirements document like this:

1
2
3
4
5
Let's first create a requirements analysis markdown document to implement the ... feature. You need to list each main requirement, including:

- User Story
- Functional Requirements
- Acceptance Criteria

The LLM might also include sections like implementation priority, API design specifications, etc., in this document. We must modify this document as needed, not use it as-is.

Data

VS Code supports MCP, and there are many tutorials online, the most classic being reading data from various databases. I currently don't have this need, so I won't introduce MCP much — maybe I'll mention which MCP application is useful in a future "Developer Diary" post.

The data I most often provide in chats is the backend API, to facilitate frontend development.

1
2
You need to refer to the backend API:
#fetch http://localhost:8080/swagger/v1/swagger.json

Privacy & Data Security

The reasons I hadn't used Agent programming before also included:

First, worrying that the Agent would execute dangerous operations like sudo rm -rf /. This is purely unnecessary worry, because VS Code requires our approval for every command the Agent executes.

Second, worrying about code security. I'm actually not too worried about this, because much of my code is in private repositories on GitHub. Even if Microsoft uses my code without permission, this code isn't very valuable, so let them use it. Since the code already has the risk of leakage, leaking it to a few more LLM companies isn't a big deal. Similarly, if a company's code is on commercial platforms like GitHub and Azure DevOps, or if employees are allowed to use third-party AI programming tools, worrying about code security is scaring oneself; it's better to think about something else.

Third, worrying about privacy and data security. The reasoning here is similar to the first point, fearing that the Agent has excessive permissions and will scan my whole disk. In reality, the Agent in VS Code, at most, automatically scans files in the current folder.

If readers are concerned about the additional security issues brought by AI programming, you can refer to this article. It mainly discusses VS Code configuration, such as disabling auto-completion for certain file types (json, yaml, env). However, in practice, I found this can still lead to key information leakage. For example, when writing a development docker-compose file, I might enable auto-completion. But when configuring some containers I personally use, I might write the API key directly as an environment variable in the docker-compose file. By the time I realized Copilot was auto-completing this file, the API key had already been leaked. Of course, this can be seen as me not following best practices, but convenience and security are often difficult to achieve simultaneously. It shows that some types of files don't need auto-completion all the time, but currently, it seems we can only globally disable auto-completion for that file type, enable it when we are sure we need it, and then disable it again. This is troublesome but effectively protects security.

References