Lesson 68Phase 8: Tools of the trade
Working with AI coding assistants
Using AI tools to learn faster without handing over your understanding: asking well, checking what comes back, and testing it.
By BasithUpdated 35 min
- AI assistant
- prompting
- verification
- tests
- code review
- hallucination
AI coding assistants can write a method from a sentence, explain an error message, and suggest the next line before you type it. Used well, they make you faster and help you learn. Used badly, they hand you code that looks right, compiles, and is wrong in a way you cannot see, because you never learned what right looks like. This lesson is about the first way: asking clearly, checking every answer, and keeping the understanding for yourself.
- Say what an assistant is good at, where it goes wrong, and why.
- Ask a question with the context, goal and constraints that make a good answer likely.
- Check generated code by reading it, running it and testing its edge cases.
- Recognise an invented method from the compiler's error, and confirm a method in the documentation.
- Keep secrets and private data out of a prompt, and watch generated code for security and licensing problems.
What an assistant is, and what it is not#
A coding assistant is built on a language model: a program trained on a huge amount of text and code to predict what is likely to come next. Ask for “a method that reverses a string” and it produces the kind of code that usually follows such a request. That is why it is so good at common tasks, and also why it can be confidently wrong. It does not run your code, it does not know your project unless you show it, and it has no sense of which of its sentences are true. A wrong answer reads exactly as smoothly as a right one.
Learn with it, not instead of it#
The risk for a learner is not that the assistant is wrong. It is that it is right often enough that you stop thinking. If it writes every loop for you, you never get faster at loops, and you cannot tell a correct loop from a broken one. A few habits keep the learning on your side:
- Try first. Write your own attempt, even a bad one, before you ask. Then ask the assistant to review it, and you learn from the difference.
- Ask why, not only what. “Why does this throw a NullPointerException?” teaches you more than “fix this”.
- Make it quiz you. “Give me three small exercises on HashMap, without answers” turns it into a practice partner.
- Never paste code you could not explain line by line. If you cannot explain it, you cannot fix it when it breaks.
Asking well#
An assistant only knows what you tell it. The same question gets a much better answer when it says which Java you use, what you already have, exactly what you want, and what you do not want:
double average(int[] numbers).”average(new int[]{2, 3}) should return 2.5.”IllegalArgumentException.”Even with a good request, treat the answer as a first draft from a quick colleague who never tests anything. Which brings us to checking.
Checking what comes back#
Here is the kind of answer a vague request might get, for “the average of some numbers”. It looks fine, it compiles, and it works on the first example anyone tries. The tests below it say what the method should do, including the cases nobody tries by hand. Run them.
import static org.junit.jupiter.api.Assertions.*;import org.junit.jupiter.api.Test; class Stats { // A suggested method for "the average of the numbers". static double average(int[] numbers) { int sum = 0; for (int n : numbers) { sum += n; } return sum / numbers.length; }} class StatsTest { @Test void averageOfThree() { assertEquals(2.0, Stats.average(new int[]{1, 2, 3})); } @Test void averageWithAHalf() { assertEquals(2.5, Stats.average(new int[]{2, 3})); } @Test void noNumbersIsAnError() { assertThrows(IllegalArgumentException.class, () -> Stats.average(new int[]{})); }}One test passes and two fail. averageWithAHalf fails with expected: <2.5> but was: <2.0>: sum and numbers.length are both int, so / is whole-number division, which throws away the half, as in operators and casting. The double return type does not help, because the division has already happened. noNumbersIsAnError fails because an empty array divides by zero, and the method throws an ArithmeticException instead of saying clearly what was wrong. Both are classic edge cases. Neither shows up when you try {1, 2, 3} and see 2.0.
Key ideaCode that compiles and works on one example is a guess. Code that passes tests for the edge cases is checked. Write the tests yourself, because they are where your understanding of the problem lives.
average so all three tests pass. Throw an IllegalArgumentException for an empty array, and make the division happen in double. Leave the tests alone.Methods that do not exist#
Assistants sometimes use a method that sounds right but is not there, often borrowed from another language or library. Lists in Java have no sum(). The compiler catches this one for you:
$ javac Main.javaMain.java:6: error: cannot find symbol int total = prices.sum(); ^ symbol: method sum() location: variable prices of type List<Integer>1 error“Cannot find symbol” on a method you were given is the signal to open the API documentation for the type, here List, and see what it really offers. (The working version sums a list with a loop, or with prices.stream().mapToInt(Integer::intValue).sum(), as in streams.) The more dangerous inventions compile: an option that is quietly ignored, a method from an older version that now behaves differently, or a setting that does nothing. Only reading the documentation and testing catch those.
Secrets, security and licences#
What you type into an assistant usually leaves your computer and is sent to someone else's servers. That makes three rules worth keeping, even for a practice project.
- Never paste secrets or private data. No passwords, API keys, tokens or connection strings, and no real customer names, emails or records. Replace them with made-up values before you ask. If a secret does get pasted, treat it as leaked and change it.
- Check generated code for security holes. Assistants reproduce common patterns, including common mistakes: SQL built by joining strings with user input (open to SQL injection, as creating and executing queries shows in the next phase), passwords stored as plain text, or error messages that reveal too much. Read generated code that touches input, files, databases or the network as carefully as code from a stranger.
- Follow your project's rules on licences. Generated code can closely resemble code that someone published under a licence. Many companies have a policy on which tools may be used and how; at school or at work, ask before you paste generated code into a shared project.
Try it
Answer each one in your head or on paper first, then open the answer.
return sum / numbers.length; return 2.0 for {2, 3} even though the method returns a double?scanner.nextString() to read a word. How do you find out whether that is right, and what would you use instead?Practice
Work through these on your own. Each one builds on this page.
int countVowels(String text) before you write the method: one ordinary case, one empty string, and one with capital letters. Then ask an assistant for the method, run your tests against its answer, and fix it if it fails.Further reading#
Spotted a mistake, or a part that did not make sense? Report it by email