What’s Covereted
- AI Generated Code Current State
- AI Generated Code Anti-pattern
- Example of prompts for high quality code Generation

Introduction
Okay, so you’ve decided to start using AI to generate code. The tool could be Windsurf, Copilot, Cline, Cursor—any of these. By the time this post is published, there might be even more options available. So don’t stress over which tool to use. Every day, we see flashy demos claiming their “Hello World” is the next big production-ready application. But the reality is far more complex. Production-grade applications are intricate and require careful craftsmanship on top of the code generated by LLMs.
As the AI world evolves rapidly, a significant portion of coding tasks can now be offloaded to LLM-based coders. It’s crucial to leverage them fully to boost productivity. Remember the quote from Andrew Ng: AI will not replace human jobs, but humans with AI skills will replace those without them. The future belongs to HI (Human Intelligence) + AI (Artificial Intelligence). While low-level tasks will get offloaded to LLMs, you need to level up your skills to thrive in the next-gen tech world. If a production issue arises, you must be able to fix it—with or without AI. So, while generating code with LLMs is beneficial, maintaining control over the process is equally important to ensure the reliability of production deployments.
We’ve been using these LLMs extensively and have observed both improvements and challenges in AI-generated code. Keeping control doesn’t just mean selecting what to generate but also ensuring its quality. Best practices around design, architecture, maintainability, performance, and scalability must still be followed. Code craftsmanship matters. Design patterns play a key role—they are proven solutions to recurring problems. Recognizing and applying them can lead to well-structured, maintainable applications. For example, the Bulkhead pattern is commonly used to contain failures within a specific area of an application.
The Danger of Anti-Patterns in AI-Generated Code
However, alongside patterns, there are anti-patterns—practices that seem beneficial initially but cause long-term problems. These bad practices often appear to solve an issue quickly but lead to inefficiencies and maintenance nightmares down the road. The tricky part? They can feel like the correct approach at first.
Let’s examine some anti-patterns in AI-generated code. When using an LLM, the first generated solution might seem like the best one—but in the long run, it could create more issues than it solves. Below, we’ll discuss some common AI-related anti-patterns and how to defend against them.
1. Blackbox Code Mentality (Cargo Cult Programming)
Anti-pattern: Blindly accepting AI-generated code without understanding its inner workings.
Defense:
- Conduct thorough code reviews with experienced developers.
- Use step-by-step debugging to trace the code’s execution.
- Require AI to provide explanations or documentation alongside the code.
2. Lack of Maintainability
Anti-pattern: AI generates code that is difficult to modify or extend.
Defense:
- Enforce coding standards and style guides.
- Ensure AI-generated code is well-documented and modular.
- Regularly refactor AI-generated code to improve its structure.
3. Dependency Hell
Anti-pattern: AI introduces excessive or conflicting dependencies.
Defense:
- Use version control and dependency management tools.
- Regularly review and remove unnecessary dependencies.
- Limit AI-generated code to well-defined modules to prevent bloated dependency trees.
4. Inefficient Code & Unoptimized Algorithms
Anti-pattern: AI may generate inefficient loops, recursive functions, or brute-force approaches.
Defense:
- Perform benchmarking & profiling to detect performance bottlenecks.
- Use Big-O analysis to assess algorithmic efficiency.
- Manually refactor AI-generated logic when necessary.
5. Over-Reliance on AI & Skill Atrophy
Anti-pattern: Developers become overly dependent on AI, leading to a decline in their coding skills.
Defense:
- Use AI as a tool to augment, not replace, human developers.
- Encourage continuous practice and skill improvement.
- Treat AI-generated code as a learning resource to understand different coding patterns better.
By recognizing these anti-patterns and applying defensive strategies, developers can harness AI’s power while maintaining code quality and security. AI-generated code can accelerate development, but blind trust in its output is a major risk. The best practice is to use AI as an assistant, not a replacement for human judgment.
Now, let’s look at some prompting strategies and generated code examples. (I’m using Claude 3.5 Sonet Preview in GitHub Copilot.)
Basic Prompt
Generate an ASP.NET Web API endpoint to save user data to a SQL database.
Generated Code
using System.ComponentModel.DataAnnotations;
namespace YourNamespace.Models
{
public class User
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Phone]
public string PhoneNumber { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
using YourNamespace.Models;
namespace YourNamespace.Data
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<User> Users { get; set; }
}
}
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using YourNamespace.Data;
using YourNamespace.Models;
namespace YourNamespace.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly ApplicationDbContext _context;
public UsersController(ApplicationDbContext context)
{
_context = context;
}
[HttpPost]
public async Task<ActionResult<User>> CreateUser(User user)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
_context.Users.Add(user);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
[HttpGet("{id}")]
public async Task<ActionResult<User>> GetUser(int id)
{
var user = await _context.Users.FindAsync(id);
if (user == null)
return NotFound();
return user;
}
}
}
Many more I removed for bravety. However, below are the issues with this code.
- No validation of input (User model).
- No Proper Logging
- No API Documentation
- And many more more…..
Improved Prompt
Generate an ASP.NET Web API endpoint to save user data to a SQL database using Entity Framework Core. Include input validation, asynchronous operations, dependency injection for the DbContext, proper error handling, and Swagger documentation. Explain the security measures used.
Generated Code
using System.ComponentModel.DataAnnotations;
namespace YourApi.Models
{
public class User
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; } = string.Empty;
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
[Phone]
public string? PhoneNumber { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? LastModifiedAt { get; set; }
}
}
DTO Added
namespace YourApi.Models.DTOs
{
public class CreateUserDto
{
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string? PhoneNumber { get; set; }
}
}
DTO Validation Added 🙂
using FluentValidation;
using YourApi.Models.DTOs;
namespace YourApi.Validators
{
public class CreateUserDtoValidator : AbstractValidator<CreateUserDto>
{
public CreateUserDtoValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(100)
.Matches("^[a-zA-Z ]*$").WithMessage("Name can only contain letters and spaces");
RuleFor(x => x.Email)
.NotEmpty()
.EmailAddress()
.MaximumLength(255);
RuleFor(x => x.PhoneNumber)
.Matches(@"^\+?[1-9]\d{1,14}$").When(x => !string.IsNullOrEmpty(x.PhoneNumber))
.WithMessage("Invalid phone number format");
}
}
}
Audit Details added. You may have asked for more details.
using Microsoft.EntityFrameworkCore;
using YourApi.Models;
namespace YourApi.Data
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<User> Users => Set<User>();
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
var entries = ChangeTracker
.Entries()
.Where(e => e.Entity is User && e.State is EntityState.Modified);
foreach (var entry in entries)
{
((User)entry.Entity).LastModifiedAt = DateTime.UtcNow;
}
return base.SaveChangesAsync(cancellationToken);
}
}
}
Controller Details
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using YourApi.Data;
using YourApi.Models;
using YourApi.Models.DTOs;
namespace YourApi.Controllers
{
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class UsersController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly ILogger<UsersController> _logger;
public UsersController(ApplicationDbContext context, ILogger<UsersController> logger)
{
_context = context;
_logger = logger;
}
/// <summary>
/// Creates a new user.
/// </summary>
/// <param name="userDto">The user data.</param>
/// <response code="201">Returns the newly created user</response>
/// <response code="400">If the user data is invalid</response>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<User>> CreateUser(CreateUserDto userDto)
{
try
{
var user = new User
{
Name = userDto.Name,
Email = userDto.Email,
PhoneNumber = userDto.PhoneNumber
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
_logger.LogInformation("User created successfully: {UserId}", user.Id);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating user");
return StatusCode(500, "An error occurred while creating the user");
}
}
/// <summary>
/// Retrieves a specific user by ID.
/// </summary>
/// <param name="id">The user ID</param>
/// <response code="200">Returns the requested user</response>
/// <response code="404">If the user is not found</response>
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<User>> GetUser(int id)
{
var user = await _context.Users.FindAsync(id);
if (user == null)
{
_logger.LogWarning("User not found: {UserId}", id);
return NotFound();
}
return user;
}
}
}
Middleware added
using FluentValidation;
using FluentValidation.AspNetCore;
using Microsoft.EntityFrameworkCore;
using YourApi.Data;
using YourApi.Models.DTOs;
using YourApi.Validators;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddControllers();
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddValidatorsFromAssemblyContaining<CreateUserDtoValidator>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "User Management API", Version = "v1" });
// Include XML comments for Swagger documentation
var filePath = Path.Combine(AppContext.BaseDirectory, "YourApi.xml");
c.IncludeXmlComments(filePath);
});
// ...existing code...
namespace YourApi.Middleware
{
public class SecurityHeadersMiddleware
{
private readonly RequestDelegate _next;
public SecurityHeadersMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
context.Response.Headers.Add("Referrer-Policy", "strict-origin-origin-when-cross-origin");
context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'");
await _next(context);
}
}
}
Improved Areas
- Input Validation:
- Security Headers:
- Audit Trail:
- Error Handling:
- API Documentation:
- ….
Next Prompt
Explain why you used AddAsync instead of Add, and how SaveChangesAsync avoids thread pool starvation.
Generated Output
Let me explain the key differences and benefits of using async methods in Entity Framework Core:AddAsync
vsAdd
Actually, in my previous response, we should stick withAdd
instead ofAddAsync
. Here’s why:AddAsync
is only useful when using identity columns that need to get their value from a database sequence orHiLo
patterns
For normal entity tracking operations,Add
is sufficient since it’s a synchronous operation that just marks the entity for insertion
Microsoft’s documentation specifically notes thatAddAsync
provides no benefits for normal scenarios
Here’s the corrected controller code:
You can see the what’s going on here 🙂
public async Task<ActionResult<User>> CreateUser(CreateUserDto userDto)
{
try
{
var user = new User
{
Name = userDto.Name,
Email = userDto.Email,
PhoneNumber = userDto.PhoneNumber
};
_context.Users.Add(user);
// Use ConfigureAwait(false) since we don't need context after the save
await _context.SaveChangesAsync().ConfigureAwait(false);
_logger.LogInformation("User created successfully: {UserId}", user.Id);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating user");
return StatusCode(500, "An error occurred while creating the user");
}
}
You can see we are able to generate better designed code using prompts here on what are we looking for than fully relying on LLM to generate code for you. 🙂 It means we need to keep upgrading ourself on fundamentals very well.
Please comment how much percentage of code you have been able to generate which was put in the production. I would like to understand how is evolution is going for you.
Happy AI Coding to you….