We have all seen the magic. You ask ChatGPT to "write a Python script to scrape a website and save it to a CSV," and thirty seconds later, you have working code. It’s impressive. It’s fast. It’s intoxicating.

But then you look closer.

The entire logic—HTTP requests, HTML parsing, data transformation, and file I/O—is jammed into a single 200-line function. Hardcoded dependencies are everywhere. Changing the output format from CSV to JSON requires rewriting half the script.

The AI gave you working code, but it didn't give you maintainable code. It gave you technical debt on day zero.

This is the central challenge of using Generative AI in professional software engineering right now. LLMs are trained on the entirety of the internet’s code, which includes a lot of bad habits. They default to the path of least resistance, which is usually a tightly coupled mess.

If we want to use AI to build serious systems, we have to stop asking it to just "write code" and start asking it to engineer solutions. We do this by baking architectural constraints—specifically the SOLID principles—directly into our prompts.

The Difference a Prompt Makes: A Case Study

Let's look at a simple requirement: We need a service that fetches user data from an external API and sends a welcome email.

The Lazy Prompt (and the resulting mess)

Prompt:

"Write a TypeScript class that fetches a user from https://api.example.com/users/{id} and then sends them a welcome email using SES."

AI Output (The "Junior Dev" Special):

import axios from 'axios';
import * as AWS from 'aws-sdk';

class UserService {
  async registerUser(userId: string) {
    // 1. Fetching logic tightly coupled to Axios and a specific URL
    const response = await axios.get(`https://api.example.com/users/${userId}`);
    const user = response.data;

    // 2. Email logic tightly coupled to AWS SES
    AWS.config.update({ region: 'us-east-1' });
    const ses = new AWS.SES();
    const params = {
      Destination: { ToAddresses: [user.email] },
      Message: { /* ... boilerplate ... */ },
      Source: '[email protected]',
    };

    await ses.sendEmail(params).promise();
    console.log('User registered and email sent.');
  }
}

Why this fails SOLID:

The Engineered Prompt (The "Senior Architect" Approach)

To fix this, we don't need to write the code ourselves. We just need to tell the AI exactly what architectural pattern to follow.

Prompt:

"I need a TypeScript class UserRegistrationService that orchestrates the process of fetching a user and sending a welcome email.

Crucially, this class must adhere to SOLID principles, specifically Dependency Inversion.

  1. Define interfaces for a UserRepository (for fetching data) and an EmailSender (for sending emails).
  2. The UserRegistrationService must receive concrete implementations of these interfaces via its constructor (constructor injection).
  3. Do NOT use any concrete libraries (like axios or aws-sdk) inside the service class itself. Only use the interfaces.
  4. Show me the service class and the interfaces."

AI Output (SOLID Gold):

// --- Interfaces (Contracts) ---

// The service doesn't care WHERE data comes from (API, DB, file)
interface UserRepository {
  getUserById(id: string): Promise<User>;
}

// The service doesn't care HOW emails are sent (SES, SendGrid, Log file)
interface EmailSender {
  sendWelcomeEmail(email: string, name: string): Promise<void>;
}

interface User {
    id: string;
    email: string;
    name: string;
}


// --- The High-Level Policy (The Application Logic) ---

class UserRegistrationService {
  // Dependencies are injected, not instantiated internally.
  constructor(
    private readonly userRepo: UserRepository,
    private readonly emailSender: EmailSender
  ) {}

  async registerUser(userId: string): Promise<void> {
    // Step 1: Get the user using the abstract repository contract
    const user = await this.userRepo.getUserById(userId);

    if (!user) {
        throw new Error("User not found");
    }

    // Step 2: Send email using the abstract email sender contract
    await this.emailSender.sendWelcomeEmail(user.email, user.name);

    console.log(`User ${userId} registered successfully.`);
  }
}

Look at the difference. The AI didn't just write code; it designed a small system.

The Blueprint for SOLID Prompts

You can apply this to almost any generation task. Here is a checklist for engineering your prompts for architectural quality:

  1. Define the Role: Start by setting the context. "Act as a Senior Software Architect obsessed with clean, maintainable code."
  2. Name the Principle Explicitly: Don't beat around the bush. "Ensure this code adheres to the Single Responsibility Principle. Break down large functions if necessary."
  3. Demand Abstractions: If your code involves external systems (databases, APIs, file systems), explicitly ask for interfaces first. "Define an interface for the data layer before implementing the business logic."
  4. Force Dependency Injection: This is the single most effective trick. "The main business logic class must not instantiate its own dependencies. They must be provided via constructor injection."

Conclusion

Generative AI is a mirror. If you give it a lazy, vague prompt, it will reflect back lazy, vague code. But if you provide clear architectural constraints, it can be a powerful force multiplier for producing high-quality, professional software.

Don't just ask AI to code. Ask it to the architect.