Why AI-Generated Code Feels Weird
Source: Dev.to
The “AI‑like” Code Feel
You’ve probably felt it. You’re reviewing a pull request, or maybe you’re inheriting a new module, and something about the code just feels… off. It runs fine, the logic is correct, but it has this sterile, textbook‑perfect vibe that real developers rarely produce. It’s the uncanny valley of software—code that looks human, but isn’t.
After staring at thousands of lines from both humans and LLMs, certain patterns become glaringly obvious. Below is a deep‑dive into the tells, written from the trenches.
1. Formatting — “Perfectly Symmetrical”
What it looks like
Imagine opening a file where every line is neatly wrapped at exactly 80 characters, every indentation is a perfect 4 spaces, and there’s a symmetrical, almost rhythmic, spacing around every operator and after every comma. Braces are always on the same line (or always on the next), and files are organized with a rigid, cookie‑cutter structure.
Why it stands out
This is the output of a model trained on style guides, not a person typing under a deadline. Humans are inconsistent. We might use 2 spaces in one file and 4 in another. We let lines run long if we’re in a hurry. We might put the opening brace on a new line for a function but keep it inline for a short if statement. This “over‑consistent” formatting lacks the natural drift that comes from context‑switching, fatigue, or simply not caring about aesthetic perfection in a one‑off script.
How humans usually differ
A human‑written codebase often has slight formatting variations across files, especially if multiple people have touched it. You’ll see a mix of tab widths, occasional trailing whitespace, and inconsistent blank lines. It feels lived‑in.
JavaScript Example
// AI‑like: Perfectly symmetrical, regimented.
function calculateTotal(items) {
let total = 0;
for (let item of items) {
total += item.price;
}
return total;
}
// Human‑like: A bit looser, pragmatic.
function calcTotal(items) {
let t = 0;
for (const item of items) t += item.price;
return t;
}
2. Commenting — “Obvious Documentation”
What it looks like
Every function, no matter how trivial, has a formal docstring block. Line‑by‑line comments explain what the code is doing, not why. You’ll see novellas for a simple adder function:
"""Adds two numbers together and returns the result."""
Comments feel like subtitles for someone reading the code aloud.
Why it stands out
AI is trained to “document” by describing syntax, not intent. It lacks the human judgment to know when a comment is superfluous. Perfect, uniform commenting across an entire codebase is a huge red flag. Humans comment sparingly, usually to explain a non‑obvious why, a tricky workaround, or a // TODO:.
How humans usually differ
Human code is under‑documented. We comment only where it hurts—where the logic is complex, the business rule is obscure, or we’re leaving a warning for our future selves. Throwaway scripts and internal helpers often have zero comments.
Python Example
# AI‑like: Commenting the obvious.
# Create a list of numbers from 0 to 9
numbers = list(range(10))
# Loop through the list
for num in numbers:
# Print the current number
print(num)
# Human‑like: Commenting the "why."
# Use a fixed seed for reproducible test failures
random.seed(42)
# TODO: This breaks on February 29th, needs proper date lib
def is_weekday(date_str):
...
3. Naming — “Hyper‑Descriptive or Vaguely Generic”
What it looks like
Variable and function names are either hyper‑descriptive (calculate_total_amount_from_items_list) or extremely generic (temp, data, value1) within the same file. The names are syntactically perfect but soulless—devoid of the slang, shorthand, or personal quirks you see in human code (e.g., getStuff() or final_final_parser_v3).
Why it stands out
LLMs stitch together naming patterns from their training data, which ranges from meticulously named open‑source projects to hastily written snippets. Without true intent, the names lack a consistent “voice.” They’re either robotically precise or oddly vague, and the style can shift dramatically within a single file.
How humans usually differ
Humans develop a personal or team naming culture. We might be consistently terse (getX) or consistently descriptive, but we’re rarely both in the same scope. We also break our own rules when we’re tired, leading to foo, bar, or thingy in a quick prototype.
Python Example
# AI‑like: Inconsistent, overly verbose, or oddly generic.
def process_user_input_data(input_data_string):
parsed_data = json.loads(input_data_string)
result = perform_calculation(parsed_data)
return result
def helper(a, b): # Suddenly super generic.
return a + b
# Human‑like: More consistent tone with accepted shorthand.
def parse_input(json_str):
data = json.loads(json_str)
return calc(data)
def add(a, b):
return a + b
4. Over‑Abstraction — “Design‑Pattern Overkill”
What it looks like
A simple 20‑line script is refactored into a class with three helper methods and an abstract base “just in case.” There’s an overuse of design patterns like singletons or factories for problems that don’t need them. You’ll see enumerate() where a simple loop would do, or unnecessary try…except blocks wrapping every operation.
Why it stands out
AI models are trained on “best‑practice” examples, which often emphasize abstraction, reusability, and defensive patterns. They tend to over‑apply these patterns, resulting in code that feels academic and over‑engineered for the task at hand. It’s solving for textbook correctness, not for shipping.
How humans usually differ
Senior developers know when to abstract and when to keep it simple. We often write straightforward, slightly duplicative code first and only refactor when duplication becomes a real problem. We avoid creating classes for things that are just functions.
JavaScript Example
// AI‑like: Over‑abstracted for a simple task.
class DataProcessor {
constructor(data) {
this.data = data;
}
validate() { /* ... */ }
normalize() { /* ... */ }
process() {
this.validate();
this.normalize();
// ... actual processing logic
}
}
// Human‑like: Simple functional approach.
function processData(data) {
// Validate and normalize inline when needed.
if (!Array.isArray(data)) throw new Error('Invalid data');
return data.map(item => /* ... */);
}
Takeaway
The “AI‑like” vibe isn’t about what the code does—it’s about how it’s written. Over‑consistent formatting, exhaustive but shallow comments, naming that swings between ultra‑precise and generic, and unnecessary abstraction are strong signals that a piece of code may have been generated by a model rather than a human developer working under real‑world constraints. Recognizing these patterns helps you spot AI‑generated code and, more importantly, guides you toward writing code that feels lived‑in, maintainable, and genuinely human.
Processing
// Human‑like: a plain function that gets the job done.
function processData(data) {
if (!data) return null;
// quick validation and normalization inline
const cleaned = data.map(item => ({ ...item, value: Number(item.value) }));
// ... process
return cleaned;
}
AI‑like defensive style
What it looks like: every function has a blanket
try…catchthat logs a generic error message like “An error occurred.” Edge cases are handled obsessively, even for scripts that will run once in a controlled environment. You might see custom error classes for a simple CLI tool.Why it stands out: the model is trained on code that aims to be robust, so it defaults to a “bubble‑wrap” approach. It lacks the contextual understanding to know when defensive coding is essential (e.g., a payment service) and when it’s overkill (e.g., a one‑time data‑migration script).
How humans usually differ: we’re pragmatic. In production code we handle specific, likely errors. In scripts we might let things crash and read the stack trace. We often comment
// assuming the input is valid for nowor// FIXME: add proper error handlingas a placeholder.
Python – AI‑like vs Human‑like
# AI‑like: Defensive to a fault.
def read_config(path):
try:
with open(path, 'r') as f:
data = json.load(f)
return data
except FileNotFoundError:
print("File not found.")
return None
except json.JSONDecodeError:
print("Invalid JSON.")
return None
except Exception as e:
print(f"An error occurred: {e}")
return None
# Human‑like: often more direct, handling errors where it matters.
def read_config(path):
with open(path) as f:
return json.load(f) # Let it crash if the file is missing or malformed.
Import bloat vs lean imports
What it looks like: the code uses the safest, most conservative defaults. It avoids “hacky” but common solutions (e.g., using
setTimeout(..., 0)for deferring execution in JavaScript). Imports are often bloated, bringing in every library that might be relevant, leading to unused import statements at the top of files.Why it stands out: AI models are optimized to generate correct, widely acceptable code. They shy away from idiomatic shortcuts that are technically impure but practically useful. The import bloat happens because the model tries to ensure all possible dependencies are available—a “just‑in‑case” approach.
How humans usually differ: we use language‑specific idioms and sometimes employ clever hacks (with a comment explaining why). We trim imports ruthlessly and only add a library after we’ve confirmed we need it.
Python – AI‑heavy vs Human‑lean imports
# AI‑like: Conservative and import‑heavy.
import numpy as np
import pandas as pd
import re, json, os, sys, time, datetime # Many unused.
def find_pattern(text):
return re.search(r'\d+', text)
# Human‑like: lean imports, idiomatic hack.
import re
def find_pattern(text):
# re.search returns None if not found, which is falsy.
return re.search(r'\d+', text) or 0
Consistency vs Human “fatigue”
What it looks like: if you look across multiple files generated by the same AI, you’ll notice an eerie repetition in phrasing, structure, and even variable‑name choices. There’s a symmetry and polish that humans rarely maintain consistently. The code lacks “fatigue”—there are no rushed, sloppy sections that were written at 2 a.m.
Why it stands out: an AI doesn’t get tired, bored, or impatient. Its output is statistically regular, drawn from the most common patterns in its training data. Humans, on the other hand, have good days and bad days, which shows in the code.
How humans usually differ: our code tells a story. The careful, well‑commented function written at the start of a project contrasts with the messy, hard‑coded function added during a late‑night bug fix. This variation is a signature of human authorship.
The difference often boils down to intent. Junior developers might write messy code, but it’s mess with a personal fingerprint. Senior developers write clean code, but it’s clean with purpose—not perfection.
Why production code is messier
Real‑world code is shaped by conflicting requirements, tight deadlines, legacy constraints, and bug fixes. It accumulates scars (// HACK:), temporary workarounds, and layers of history. AI‑generated code, in contrast, often feels like a green‑field project that never had to endure the chaos of maintenance.
Where AI code feels “too careful”
It’s like a student who has memorized the textbook but hasn’t yet learned when to break the rules for practical gain. The code is correct, but it lacks the confident, sometimes ruthless, pragmatism of an experienced developer who knows what to ignore.
Spotting AI‑generated code
Spotting AI‑generated code isn’t about finding bugs—it’s about spotting the absence of human fingerprints: the lack of inconsistency, the surplus of explanation, and the over‑application of textbook patterns.
“Any sufficiently advanced technology is indistinguishable from magic.”
— Arthur C. Clarke
Extra: AI‑looking code is often generated by multiple AIs working together, which can amplify the “over‑engineered” feel.