Writing code is only one part of software development.
The harder skill is changing code without losing control of the system.
A small request can sound simple:
“Change this button.”
But that button might be connected to:
- an HTML element,
- a CSS class,
- a JavaScript event,
- a component,
- an API request,
- application state,
- a database operation,
- automated tests,
- and other parts of the project.
That is why professional development requires more than knowing how to type syntax.
You need to understand where a change belongs, what depends on it, what could break, and how to verify the result.
This chapter continues the VS Code series by focusing on one of the most important development skills:
How to navigate, understand, modify, and refactor a codebase safely.
A useful mental model is:
FIND → UNDERSTAND → CHANGE → REVIEW → TEST → VERIFY
VS Code provides tools that make each stage easier.
1. STOP THINKING OF CODE AS ISOLATED TEXT
When you first learn programming, a file can feel like a document.
You open it.
You type code.
You save it.
But software is not simply a collection of text files.
Code contains relationships.
Consider:
function calculateTotal(price, quantity) {
return price * quantity;
}
Another part of the program may contain:
const total = calculateTotal(price, quantity);
The function and its caller are connected.
If you rename:
calculateTotal
you may need to update every place that refers to it.
That is why editing software requires a mental model of dependencies and relationships.
2. LEARN TO ASK "WHERE DOES THIS COME FROM?"
Suppose you see:
calculateTotal(price, quantity)
Don't immediately change it.
First ask:
Where is
calculateTotaldefined?
Then:
Where else is it used?
Then:
What assumptions does it make about
priceandquantity?
Then:
What happens if I change its behavior?
These questions transform editing from guesswork into investigation.
The development process becomes:
CODE YOU SEE → RELATED CODE → SYSTEM BEHAVIOR → SAFE CHANGE
3. USE GO TO DEFINITION
Modern development environments can often help you move from a symbol to its definition.
Suppose you see:
calculateTotal(price, quantity);
You want to find the function itself.
Instead of searching manually through dozens of files, use the editor's navigation features when language support provides them.
This is especially useful for:
- functions,
- classes,
- variables,
- components,
- methods,
- types,
- modules,
- and other symbols.
The important lesson is:
Learn to navigate code by meaning, not just by filename.
4. UNDERSTAND SYMBOLS
A symbol is a named element in source code.
Depending on the programming language, symbols can include:
- functions,
- classes,
- variables,
- constants,
- methods,
- interfaces,
- types,
- modules,
- and other declarations.
For example:
const taxRate = 0.075;
function calculateTax(amount) {
return amount * taxRate;
}
Here you have symbols such as:
taxRate
calculateTax
As programs become larger, understanding and navigating symbols becomes much more useful than simply reading files from top to bottom.
5. USE GO TO REFERENCES
Finding where something is defined is only half the problem.
You also want to know:
Where is this used?
Suppose you find:
function calculateTax(amount) {
return amount * taxRate;
}
You may discover that it is called from:
checkout.js
invoice.js
order.js
reports.js
Now you understand the potential impact of changing it.
This is an important development principle:
Before changing shared code, understand who depends on it.
The more widely used a component is, the more carefully you should change it.
6. SEARCH IS YOUR CODEBASE MAP
Workspace search is one of the most useful tools in VS Code.
Imagine a project containing:
src/
├── components/
├── pages/
├── services/
├── utils/
└── tests/
You encounter:
customerId
Searching the entire workspace might reveal:
components/CustomerCard.js
pages/Profile.js
services/customer.js
services/orders.js
tests/customer.test.js
Now you can trace the concept through the application.
Search turns a large codebase into something you can explore.
7. SEARCH FOR CONCEPTS, NOT ONLY FILE NAMES
A common beginner approach is:
“I need the customer page, so I'll open
Customer.js.”
But the relevant code may not be in that file.
The word you're looking for might appear in:
- a component,
- a database query,
- an API endpoint,
- a configuration file,
- a test,
- or a documentation file.
Searching for the concept can therefore be more effective than guessing the filename.
For example:
customer
checkout
payment
login
dashboard
profile
Search becomes a way of asking the codebase questions.
8. USE FIND AND REPLACE CAREFULLY
Suppose a project contains:
oldProductName
and you want:
newProductName
You could replace every occurrence.
But not every occurrence necessarily means the same thing.
The word might appear in:
- source code,
- comments,
- documentation,
- test data,
- URLs,
- configuration,
- database examples,
- or unrelated text.
So use this workflow:
FIND → REVIEW MATCHES → REPLACE INTENTIONALLY → TEST
Never assume that identical text means identical meaning.
9. UNDERSTAND RENAME REFACTORING
There is an important difference between:
Text replacement
Changing matching characters.
and:
Symbol renaming
Changing a programming symbol while respecting the language's understanding of that symbol.
For example, if you rename:
calculateTotal
a language-aware refactoring tool may update references to the function without blindly replacing unrelated text that happens to contain the same characters.
Where supported, use language-aware refactoring for programming changes.
It is generally safer than treating source code as ordinary prose.
10. WHAT IS REFACTORING?
Refactoring means changing the internal structure of software without intentionally changing its externally expected behavior.
Suppose you have:
function calculateInvoiceTotal(items) {
// a very long block of code
}
You might break part of it into:
function calculateSubtotal(items) {
// subtotal logic
}
function calculateTax(subtotal) {
// tax logic
}
function calculateInvoiceTotal(items) {
const subtotal = calculateSubtotal(items);
return subtotal + calculateTax(subtotal);
}
The system may still produce the same result.
But the structure becomes easier to understand and maintain.
That is refactoring.
11. WHY REFACTORING MATTERS
Software changes continuously.
A project that begins with:
10 files
might eventually become:
100 files
and then:
1,000+ files
If every change adds more complexity without improving structure, maintenance becomes increasingly difficult.
Refactoring helps manage that complexity.
It can improve:
- readability,
- organization,
- reuse,
- maintainability,
- testability,
- and separation of responsibilities.
But refactoring also carries risk.
A structural change can accidentally change behavior.
That is why refactoring should be paired with testing.
12. SEPARATE "CHANGE BEHAVIOR" FROM "CHANGE STRUCTURE"
These are different types of work.
Behavioral change
You intentionally change what the software does.
Example:
Tax rate changes from 5% to 7.5%.
Structural change
You reorganize how the software works internally while attempting to preserve behavior.
Example:
Move tax calculation into a separate function.
Keeping these changes separate can make debugging easier.
If you simultaneously:
- redesign the interface,
- rewrite the database layer,
- rename functions,
- and change business rules,
then discovering what caused a failure becomes much harder.
13. MAKE SMALL CHANGES
A powerful development principle is:
Change the smallest meaningful amount of code necessary to solve the problem.
Suppose a button has the wrong color.
You probably don't need to rewrite the entire interface.
Suppose a function has one incorrect calculation.
You probably don't need to replace the whole module.
Small changes make cause and effect easier to observe.
The loop becomes:
SMALL CHANGE → TEST → OBSERVE → CONTINUE
This is one of the foundations of controlled development.
14. USE DIFFS TO REVIEW YOUR WORK
A diff shows what changed between two versions.
Imagine you began with:
const total = price * quantity;
and changed it to:
const total = price * quantity + shipping;
A diff makes that change visible.
This is extremely useful because sometimes your memory of what you changed is incomplete.
Before committing important work, inspect the changes.
Ask:
Did I change what I intended?
Did I accidentally modify something else?
Did debugging leave temporary code behind?
Did formatting alter unrelated files?
Reviewing your diff is a simple quality-control step.
15. LEARN TO READ A DIFF
A diff typically distinguishes:
- added lines,
- removed lines,
- and unchanged context.
For example:
- const total = price * quantity;
+ const total = price * quantity + shipping;
You can immediately see the behavioral change.
Now imagine a large diff containing hundreds of unexpected lines.
That should make you stop.
A large unexpected diff can indicate:
- accidental formatting,
- generated files,
- line-ending changes,
- a broad search-and-replace,
- or an unintended modification.
The diff is therefore not just a record.
It is a verification tool.
16. SAVE OFTEN, BUT TEST INTENTIONALLY
Saving your work frequently protects against losing unsaved changes.
But saving does not prove that the software works.
You need separate checkpoints:
SAVE → RUN → TEST → REVIEW
These answer different questions.
Save
Did the editor write the file?
Run
Can the program execute?
Test
Does it behave correctly?
Review
Did I change what I intended?
Don't confuse these activities.
17. UNDERSTAND SYNTAX ERRORS
A syntax error occurs when code violates the grammatical rules expected by a language.
For example:
if (userLoggedIn {
showDashboard();
}
The structure is incomplete.
The programming language cannot correctly interpret it.
The editor may highlight the problem.
But don't simply fix the red underline.
Understand why the syntax is invalid.
Programming languages are formal systems.
The compiler, interpreter, or runtime expects code to follow specific rules.
18. UNDERSTAND LOGICAL ERRORS
Some of the hardest bugs are syntactically valid.
For example:
const total = price - quantity;
The code may run.
But perhaps the intended calculation was:
const total = price * quantity;
There is no syntax error.
The problem is logic.
This creates three important categories:
SYNTAX ERROR → The code cannot be correctly interpreted.
RUNTIME ERROR → Something goes wrong while the program runs.
LOGICAL ERROR → The program runs but produces the wrong result.
Learning to distinguish these categories improves debugging dramatically.
19. TRACE THE DATA
Many bugs become easier to understand when you follow the data.
Suppose a customer enters:
quantity = 3
price = 1000
You expect:
total = 3000
But the application displays:
total = 2000
Don't immediately rewrite the calculation.
Trace the data:
USER INPUT
↓
FORM
↓
VALIDATION
↓
APPLICATION STATE
↓
FUNCTION
↓
CALCULATION
↓
DISPLAY
At which point did the value become wrong?
This method is much more powerful than guessing.
20. TRACE CONTROL FLOW
Data is not the only thing you can trace.
You can also trace which code executes and in what order.
Imagine:
USER CLICKS BUTTON
↓
EVENT HANDLER
↓
VALIDATION
↓
API REQUEST
↓
SERVER
↓
DATABASE
↓
RESPONSE
↓
UI UPDATE
If the interface doesn't update, the problem could exist at several points.
You need to determine:
How far did execution get?
This is why logs, breakpoints, network inspection, and debugging tools are valuable.
21. DON'T DEBUG THE WRONG LAYER
Suppose a page displays:
“Payment failed.”
You might immediately inspect the payment button.
But the actual problem could be:
UI
↓
API
↓
SERVER
↓
PAYMENT PROVIDER
↓
BANK
The interface might be working perfectly.
The failure may occur downstream.
A useful debugging principle is:
Locate the failing layer before changing the code.
This prevents unnecessary modifications.
22. USE LOGGING AS EVIDENCE
Logging can show what the application is doing.
For example:
console.log("Submitting order", orderId);
You might then discover that the application reaches the submission step.
Another log might show:
console.log("API response", response);
Now you can compare:
EXPECTED RESPONSE
vs.
ACTUAL RESPONSE
Logging should be purposeful.
Don't flood the application with meaningless messages.
A good log answers a question.
23. REMOVE TEMPORARY DEBUGGING CODE
After solving a problem, inspect your changes.
You may have added:
console.log(...)
temporary variables, comments, or debugging shortcuts.
Some may be useful permanently.
Others may not belong in production code.
Ask:
Is this part of the finished system?
If not, remove it.
Debugging tools should help you solve the problem without becoming unnecessary parts of the final product.
24. UNDERSTAND IMPORTS AND DEPENDENCIES
Modern applications are often divided into modules.
For example:
import { calculateTotal } from "./billing.js";
This tells you that the current file depends on something provided by another module.
That relationship matters.
If you change:
billing.js
you should understand which parts of the application depend on it.
This brings us back to the larger principle:
CODE IS CONNECTED.
A change in one place can have consequences elsewhere.
25. LEARN TO FOLLOW AN IMPORT CHAIN
Imagine:
checkout.js
↓
billing.js
↓
tax.js
The checkout module uses billing.
Billing uses tax.
If the tax calculation is wrong, editing checkout may not solve the real problem.
Follow the chain.
Ask:
Who imports this?
What does this import?
Where does the data come from?
Where does the result go?
This is how you investigate software architecture from inside the code.
26. USE OUTLINE AND SYMBOL NAVIGATION
Large files can become difficult to scan.
Symbol navigation helps you move directly to functions, classes, methods, and other important structures where supported.
Instead of scrolling through:
1,000 lines
you can think:
class User
├── constructor
├── login
├── logout
├── updateProfile
└── deleteAccount
Now the file becomes a map of responsibilities.
This reinforces an important idea:
Good navigation reduces cognitive load.
27. KEEP FUNCTIONS UNDERSTANDABLE
Consider:
function processOrder(order) {
// validate customer
// calculate tax
// calculate shipping
// update inventory
// charge payment
// send email
// create invoice
// update analytics
}
This function may be doing too much.
A more maintainable design might separate responsibilities:
validateOrder()
calculateTotals()
reserveInventory()
processPayment()
createInvoice()
sendConfirmation()
recordAnalytics()
This does not mean every tiny action needs its own function.
The goal is meaningful separation.
You want code where a developer can answer:
What responsibility does this piece have?
without reading the entire application.
28. DON'T REFACTOR JUST FOR THE SAKE OF REFACTORING
Clean-looking code is useful.
But refactoring has a cost.
If the software works and the structure is understandable, unnecessary restructuring can introduce risk without creating meaningful value.
Ask:
What problem will this refactoring solve?
Possible answers:
- duplicated logic,
- difficult testing,
- confusing responsibilities,
- excessive coupling,
- poor readability,
- difficult maintenance,
- or a requirement for future changes.
If there is no meaningful problem, don't create unnecessary work.
29. UNDERSTAND COUPLING
When two parts of a system depend heavily on each other, they are coupled.
For example:
Module A
↕
Module B
↕
Module C
If changing A requires unexpected changes in B and C, the system may have strong dependencies.
You don't need to eliminate all coupling.
Software components naturally depend on one another.
The goal is manageable coupling.
A useful question is:
If I change this component, how much of the system must I understand?
The more localized the change can be, the easier the system is generally to maintain.
30. UNDERSTAND COHESION
Cohesion asks whether the things grouped together actually belong together.
For example, a module called:
payment.js
containing payment-related functionality has a clear responsibility.
But if it also contains:
- image resizing,
- email formatting,
- database backup,
- user-interface rendering,
- and weather calculations,
the module becomes difficult to understand.
A useful design principle is:
Keep related responsibilities together and unrelated responsibilities apart.
This helps both humans and software systems.
31. USE TESTS AS A SAFETY NET
Imagine you have a function:
function calculateTotal(price, quantity) {
return price * quantity;
}
You could test:
price = 100
quantity = 2
expected = 200
Then:
price = 500
quantity = 3
expected = 1500
And perhaps boundary conditions such as:
quantity = 0
Tests give you evidence that behavior still works after changes.
This is especially valuable during refactoring.
Without tests, you may rely entirely on memory and manual checking.
32. TEST THE PART YOU CHANGED
You don't always need to run every possible test immediately.
Start with the smallest relevant verification.
If you changed:
tax calculation
test tax behavior.
If you changed:
login
test authentication behavior.
If you changed:
button styling
inspect the affected interface.
Then, depending on the project, run broader tests.
The principle is:
LOCAL VERIFICATION → BROADER VERIFICATION
33. USE VERSION CONTROL AS A SAFETY NET
Git makes experimentation safer because you can record meaningful states.
A practical workflow might be:
WORKING VERSION
↓
COMMIT
↓
EXPERIMENT
↓
TEST
↓
KEEP OR REVERT
This is one reason version control changes how developers work.
You don't have to treat every experiment as irreversible.
But remember:
Git does not automatically make bad changes safe.
You still need to understand branches, commits, remote repositories, and recovery.
34. WRITE COMMITS THAT EXPLAIN THE CHANGE
A commit message should communicate what changed.
Compare:
update
with:
Fix incorrect invoice total calculation
The second gives future-you useful information.
A good commit answers:
What meaningful change does this checkpoint represent?
This becomes increasingly valuable as project history grows.
35. REVIEW BEFORE YOU COMMIT
Before creating a commit:
- Inspect changed files.
- Review the diff.
- Remove accidental changes.
- Check debugging leftovers.
- Run relevant tests.
- Confirm the application behaves as expected.
- Commit the intentional change.
Think of the commit as:
“This is a meaningful, understood state of the project.”
That is much better than treating commits as random save points.
36. A PRACTICAL VS CODE CHANGE WORKFLOW
Suppose a client asks:
“Change the checkout button text from
Pay NowtoComplete Payment.”
Don't immediately search and replace.
Use this workflow.
Step 1: Locate
Search for:
Pay Now
Step 2: Inspect
Determine whether it is:
- actual interface text,
- test data,
- documentation,
- or something else.
Step 3: Understand
Find the component or file responsible for the button.
Step 4: Change
Modify the correct source.
Step 5: Review
Inspect the diff.
Step 6: Run
Start the application.
Step 7: Verify
Confirm the correct button changed.
Step 8: Test
Check that the button still performs its intended action.
Step 9: Commit
Record the meaningful change.
That is professional editing.
37. ANOTHER EXAMPLE: FIXING A CALCULATION
Suppose an invoice displays the wrong total.
Don't start rewriting the invoice page.
Trace the flow:
ORDER DATA
↓
PRICE
↓
QUANTITY
↓
SUBTOTAL
↓
DISCOUNT
↓
TAX
↓
SHIPPING
↓
TOTAL
↓
DISPLAY
Determine where the incorrect value first appears.
If the subtotal is correct but the total is wrong, you have narrowed the problem.
If the total is correct internally but the interface displays something else, the problem may be in the presentation layer.
This is a much stronger debugging method than changing code randomly.
38. THE "FIRST WRONG VALUE" METHOD
Here is a powerful general debugging technique:
Find the first point where reality differs from expectation.
Suppose you expect:
100 → 200 → 300 → 330
but observe:
100 → 200 → 250 → 280
The first wrong value is:
250
Investigate the operation that produced it.
You don't need to inspect every later operation first.
This method can dramatically narrow the search space.
39. BUILD A CHANGE-SAFETY HABIT
Before modifying important code, ask:
What am I changing?
Identify the exact requirement.
Where does it live?
Find the source of truth.
What depends on it?
Search references and relationships.
What could this affect?
Think about downstream behavior.
How will I verify it?
Define the test before making the change.
This creates:
CHANGE → IMPACT → VERIFICATION
rather than:
CHANGE → HOPE
40. YOUR PRACTICAL VS CODE REFACTORING EXERCISE
Return to the small project from Chapter 2.
Suppose your JavaScript currently contains:
console.log("Workspace loaded.");
Expand it into a small application.
For example:
function greet(name) {
return `Hello, ${name}!`;
}
const message = greet("Developer");
console.log(message);
Now practice:
Exercise 1 — Find the definition
Locate:
greet
Exercise 2 — Find the reference
Locate where:
greet(...)
is called.
Exercise 3 — Rename the function
Rename:
greet
to:
createGreeting
Use a language-aware rename feature where available.
Exercise 4 — Inspect the diff
Confirm that only the intended references changed.
Exercise 5 — Run it
Confirm the program still produces the expected result.
You've just practiced a real development workflow.
41. PRACTICE A CONTROLLED BUG
Change:
return `Hello, ${name}!`;
to something intentionally incorrect.
For example:
return `Hello, ${username}!`;
Now run the application.
Observe the error.
Instead of immediately searching for the answer, investigate:
- What symbol is missing?
- Where is it referenced?
- What value should have been used?
- What caused the mismatch?
- How can you correct it?
- How do you verify the correction?
This is how debugging ability develops.
42. PRACTICE A SMALL REFACTOR
Suppose you have:
const message = `Hello, ${name}!`;
console.log(message);
You could extract the greeting into:
function createGreeting(name) {
return `Hello, ${name}!`;
}
Then:
const message = createGreeting(name);
console.log(message);
Now ask:
Did the behavior change?
If not, you've performed a small structural improvement.
This teaches an important lesson:
Refactoring is controlled change, not uncontrolled rewriting.
43. WHAT YOU SHOULD NOW BE ABLE TO DO
After this chapter, you should be able to approach a codebase with a different mindset.
You should be learning to:
- open the project as a system,
- navigate files,
- search for concepts,
- locate symbol definitions,
- find references,
- trace dependencies,
- distinguish syntax, runtime, and logical errors,
- trace data,
- trace control flow,
- use diffs,
- perform small changes,
- understand refactoring,
- use language-aware renaming,
- inspect imports,
- reason about coupling,
- think about cohesion,
- test changes,
- and use Git checkpoints.
These are not merely VS Code tricks.
They are software-development skills.
VS Code simply gives you tools that make them easier to practice.
44. THE DEVELOPMENT CHANGE LOOP
Put everything together:
REQUIREMENT
↓
FIND
↓
UNDERSTAND
↓
TRACE IMPACT
↓
CHANGE
↓
REVIEW
↓
TEST
↓
OBSERVE
↓
┌───────┴────────┐
│ │
CORRECT INCORRECT
│ │
↓ ↓
COMMIT DEBUG AGAIN
This loop is much more valuable than memorizing a collection of editor commands.
It gives you a repeatable method for changing software.
THE BIGGER PICTURE
In Chapter 1, the goal was to build a professional web-development workspace.
In Chapter 2, the goal was to turn that workspace into a development workbench.
Now the next step is learning to operate inside the codebase without losing control of it.
That requires a shift in thinking.
Don't ask only:
“Where do I type the code?”
Ask:
“Where does this behavior come from?”
Don't ask only:
“How do I change this?”
Ask:
“What else depends on this?”
Don't ask only:
“Did the code save?”
Ask:
“Did the system behave correctly after the change?”
Don't ask only:
“Can I make this work?”
Ask:
“Can I make this work in a way that remains understandable and maintainable?”
That is the difference between editing code and engineering software.
The essential workflow is:
FIND → UNDERSTAND → CHANGE → REVIEW → TEST → VERIFY
Use it repeatedly.
The more you practice it, the less intimidating large codebases become.
And the more valuable your development skills become because you are no longer merely producing code—you are learning how to control change inside a digital system.
READ. USE. SOLVE.
Image: Fixely

No comments:
Post a Comment