AI makes writing code faster. But shipping good software still requires the same judgment it always did. Speed without engineering discipline just means shipping bugs faster.
AI is a tool in your toolset. Like a compiler, a linter, or a test runner. It doesn't own the code. You do.
When something breaks in production, nobody asks "which AI generated this?" They ask who shipped it. The PR has your name on it. The review was your responsibility. The decision to merge was yours.
You Own the Code
AI is a multiplier. If your engineering skills are weak, it multiplies that too.
TDD becomes even more powerful with AI. The engineer defines WHAT to test. AI handles the HOW.
tsx
describe('SearchFilter', () => {
it('renders input with placeholder', () => {
render();
expect(screen.getByPlaceholderText('Search products...')).toBeInTheDocument();
});
it('calls onSearch after user stops typing', async () => {
const onSearch = vi.fn();
render();
What AI Can't Do For You
expect(onSearch).not.toHaveBeenCalled();
await waitFor(() => expect(onSearch).toHaveBeenCalledWith('shoes'));
});
it('does not call onSearch for empty input', async () => {
const onSearch = vi.fn();
render();
await userEvent.type(screen.getByRole('searchbox'), 'a');
await userEvent.clear(screen.getByRole('searchbox'));
it('shows loading spinner while searching', () => {
render();
expect(screen.getByRole('status')).toBeInTheDocument();
});
Guide AI With Tests
it('trims whitespace before calling onSearch', async () => {
const onSearch = vi.fn();
render();
await userEvent.type(screen.getByRole('searchbox'), ' shoes ');
await waitFor(() => expect(onSearch).toHaveBeenCalledWith('shoes'));
});
});
You wrote zero implementation. But you defined the component's contract, its edge cases, and its behavior. That's engineering.
Refactor. You guide AI to clean up. Extract helpers, apply single responsibility, name things clearly. The goal: make it easy for the next engineer who touches this code.
Red-Green-Refactor
Without test discipline, AI gives you untested code that "looks right." With TDD, AI works within constraints you defined.
Unit tests verify pieces. E2E tests verify the whole flow works together.
AI can scaffold e2e tests, but you define which flows are critical. A checkout flow, an authentication sequence, a data export pipeline. These are decisions that require understanding the business, not just the code.
typescript
test('user completes checkout flow', async ({ page }) => {
await page.goto('/products');
await page.click('[data-testid="add-to-cart"]');
await page.click('[data-testid="checkout"]');
await page.fill('#email', 'test@example.com');
await page.fill('#card-number', '4242424242424242');
await page.click('[data-testid="place-order"]');
await expect(page.locator('.confirmation')).toBeVisible();
});
Cover Entire Flows With E2E Tests
You defined the critical path. AI can fill in the details, add assertions, handle setup/teardown. But the decision of WHAT to test end-to-end is yours. The same applies to edge cases: what happens when payment fails? When the session expires mid-checkout? When the cart is empty? You define those scenarios. AI writes the assertions.
Linting rules. Create rules that encode team conventions. AI follows them when configured, but you need to know which rules matter for your codebase.
Git hooks. Pre-push hooks that run linting and tests. Code that doesn't pass doesn't ship. No exceptions, not even for AI-generated code.
AI tool hooks. Tools like Claude Code support hooks that intercept actions and enforce standards automatically. Run lint before every commit. Run tests before every push. The AI operates within guardrails you defined.
Enforce Standards Before Code Ships
Every AI-generated change needs verification. The faster you catch problems, the faster AI can fix them.
Verification feedback loops exist at every level: CI pipelines that run visual regression tests, browser automation that captures screenshots, performance audits that flag regressions. The principle is the same. Every output becomes input for the next iteration.
In my workflow, I use Playwright MCP and Chrome DevTools MCP to close this loop directly inside the AI session:
This turns AI from "generate and hope" into "generate, verify, iterate." The engineer who sets up this loop gets better results than one who just prompts and ships.
The skill isn't writing the code. It's knowing what to verify and how to feed that information back.
AI-generated code compiles. It passes the tests you wrote. It looks reasonable. But that doesn't mean it's good.
The skill of reading code critically matters more when someone (or something) else writes it. You can't review what you don't understand. Invest in reading code as much as writing it.
typescript
// Loop through the array and filter items
const filtered = items.filter(item => item.active);
Review AI Output Like You'd Review a Junior's PR
Good comments explain why something exists or what business logic it represents:
typescript
// Archived items are processed by a nightly batch job, not shown in the UI
const filtered = items.filter(item => item.active);
But before writing any comment, ask: can the code explain itself? A well-named function or variable often eliminates the need for a comment entirely. Comments should exist only when the code can't tell the full story on its own.
That's the difference between AI-generated noise and engineering judgment.
AI generates 500 lines in seconds. Your job: break it into reviewable, understandable pieces.
Extract functions. Apply single responsibility. Name things clearly. The next engineer (or the next AI session) touching this code benefits from clean structure.
This is a human judgment call. AI optimizes for the current prompt. You optimize for the project's lifetime. A function that does one thing well is easier to test, easier to reuse, and easier to replace than a monolith that "works."
- You own the code. AI is a tool, not an excuse. Your name is on the PR.
- TDD guides AI. Write failing tests first, let AI implement, then refactor.
- E2E tests catch what unit tests miss. Define the critical flows and edge cases.
- Enforce standards with linting and hooks. Guardrails before code ships.
- Close the loop. Feed screenshots, errors, and audits back to AI for better iterations.
- Review like it's a junior's PR. Read every line. Question every abstraction.
- Comment the why, not the how. Business context over implementation details.
- Small chunks > big dumps. Break AI output into pieces the next engineer can follow.
Small Chunks for the Next Engineer
AI didn't make engineering skills optional. It made them the differentiator.
AI is a tool. A powerful one. But tools don't ship software, engineers do. Study the fundamentals. Master testing. Understand your architecture. Define your standards. Then use AI to execute at a speed you never could alone.
Takeaways
Wrapping Up
Orijinal makaleyi oku →
Bu içerik otomatik olarak derlenmektedir. Tüm haklar orijinal yayıncıya aittir.