DEV Community

Mustafa ERBAY
Mustafa ERBAY

Posted on • Originally published at mustafaerbay.com.tr

AI Code Assistants: Creating Efficiency or Dependency?

Last week, while writing a simple systemd unit file for a backend service of my side product, I automatically opened Gemini Flash in my browser instead of the usual man systemd.service command. The assistant provided the structure I needed in seconds, but this reflex made me pause: Are AI code assistants truly increasing my productivity, or are they unknowingly creating a dependency that dulls my fundamental knowledge? This question has been one of the topics I've pondered most recently.

On one hand, there are incredible benefits like rapid prototyping, auto-completing boilerplate code, and instantly learning how to use an unfamiliar API. On the other hand, there are concerns about the quality of AI-generated code, security risks, and most importantly, the possibility of our problem-solving muscles as developers weakening over time. In this post, I will share my personal experiences and observations on this dilemma.

How AI Code Assistants Work and What They Bring to Me

AI code assistants are essentially large language models (LLMs) trained on massive codebases. They use the context I provide (existing code, comments, file names, etc.) and my prompt to predict the next logical piece of code. When writing a PostgreSQL query, I don't need to remember the table schema, or when defining a FastAPI endpoint, instead of getting lost in the documentation to find the correct decorator, the assistant brings the suggestion to me with a few keystrokes.

Their biggest contribution for me has been automating repetitive and mentally draining tasks. For example, when designing a new operator screen in a production ERP, instead of manually writing CRUD endpoints for the backend and basic form components for the frontend, I can ask the AI for a draft. This saves me time and allows me to focus on the core business logic. I can even instantly get commands on how to set a specific eviction policy in Redis, which is a great convenience for details I use frequently but don't always keep in mind.

💡 Quick Start

There's a pattern I frequently use when creating a Docker Compose file. When I tell the AI, "Create a Docker Compose file with PostgreSQL 16, Redis, and an Nginx reverse proxy. Nginx should listen for the FastAPI application on port 8000," it usually provides a solid starting point. Here's a simple example:

# docker-compose.yml
version: '3.8'

services:
  db:
    image: postgres:16-alpine
    restart: always
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - db_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    restart: always

  app:
    build: .
    restart: always
    ports:
      - "8000:8000"
    depends_on:
      - db
      - redis
    environment:
      DATABASE_URL: "postgresql://user:password@db/mydb"
      REDIS_URL: "redis://redis:6379/0"

  nginx:
    image: nginx:stable-alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app

volumes:
  db_data:

This kind of skeleton eliminates the mental burden of starting from scratch and moves me directly to the customization phase. Instead of manually writing an Nginx reverse proxy configuration, I can ask the AI for a basic proxy_pass setting and then add my own rate limiting and JWT validation patterns on top. I've observed a 15-20% speed increase in my daily workflow with this approach.

Code Quality and the "Copy-Paste" Trap: A Real Risk?

The quality of AI-generated code has always been a question mark for me. A solution that looks correct at first glance might actually lead to performance issues, security vulnerabilities, or maintenance difficulties. When I ask AI for help with a PostgreSQL query, it usually does a great job for simple SELECT statements. However, when complex JOINs, subqueries, or window functions are needed, it can sometimes generate inefficient queries that create N+1 query problems, use incomplete indexes, or lead to WAL bloat.

Recently, in a client project, I found a simple SQL injection vulnerability in a Python code generated by AI. It had concatenated values directly with an f-string instead of using a parameterized query. Such errors can easily be overlooked, especially when rapidly prototyping, and can pose serious security risks in a production environment. Even when creating fail2ban rules, I've noticed that the regexes suggested by AI can sometimes be too broad or too narrow, requiring me to manually revise them based on patterns from my own audit subsystem logs. This situation reminded me once again how crucial it is to review every output from AI with a critical eye.

⚠️ Security Risk: SQL Injection in AI-Generated Code

We should always manually review code examples from AI. Here's an example of a Python function that AI might sometimes generate, which is vulnerable to SQL Injection:

# Potentially WEAK AI-generated code
import psycopg2

def get_user_data(username):
    conn = psycopg2.connect(database="mydb", user="user", password="password", host="db")
    cur = conn.cursor()
    # Bad example: Concatenating parameters directly as a string
    query = f"SELECT * FROM users WHERE username = '{username}'"
    cur.execute(query)
    result = cur.fetchone()
    cur.close()
    conn.close()
    return result

# Value an attacker could enter: "' OR '1'='1"
# This becomes WHERE username = '' OR '1'='1' and could return all users.

This type of code can easily be missed by an inexperienced developer. In my production ERP, when integrating iSCSI, I always made sure to use prepared statements or the ORM's own secure parameter binding mechanisms to prevent such vulnerabilities in functions handling critical data. AI can be an accelerator in this regard, but the final check should always be human.

Productivity Boost: Speed and Repetitive Tasks

The biggest promise of AI code assistants is undoubtedly increased productivity. This promise clearly manifests itself, especially in repetitive, tedious, and boilerplate code writing tasks. The time I spend creating a simple data table or form component in a Vue or React frontend has been cut in half thanks to AI. AI can often generate clean and readable code blocks that adhere to the framework's latest best practices. This is especially valuable when starting a new project or adding a new module to an existing one.

For example, when creating a basic .gitlab-ci.yml or .github/workflows file for a CI/CD pipeline, I can ask the AI for a draft by specifying the language and framework structure of my current project. This way, instead of writing build, test, and deploy stages from scratch, I can integrate my own rolling deploy or blue-green strategies on top of the basic structure provided by AI. This significantly reduces not only writing time but also the time spent reading documentation and searching for the correct syntax. Since I use Docker Compose-based deployments on my own servers, such automations are life-savers for me.

ℹ️ AI's Role in CI/CD Processes

Creating a basic structure for a GitLab CI pipeline is significantly accelerated with AI. Here's an example of a scenario and output you might request from AI:

Prompt: "Create a GitLab CI/CD pipeline for a Python-based FastAPI application. Tests should run with pytest, a Docker image should be built and pushed to Docker Hub when pushed to the main branch. Use caching."

AI's Potential Output:

# .gitlab-ci.yml
image: docker:latest
services:
  - docker:dind

variables:
  DOCKER_HOST: tcp://docker:2375/tcp
  DOCKER_TLS_CERTDIR: ""
  DOCKER_REGISTRY_USER: $CI_REGISTRY_USER
  DOCKER_REGISTRY_PASSWORD: $CI_REGISTRY_PASSWORD
  DOCKER_REGISTRY_IMAGE: $CI_REGISTRY_IMAGE

cache:
  paths:
    - .venv/

stages:
  - build
  - test
  - deploy

build_image:
  stage: build
  script:
    - docker build -t $DOCKER_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
    - docker login -u "$DOCKER_REGISTRY_USER" -p "$DOCKER_REGISTRY_PASSWORD" $CI_REGISTRY
    - docker push $DOCKER_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

test:
  stage: test
  image: python:3.10-slim-buster
  before_script:
    - python -m venv .venv
    - source .venv/bin/activate
    - pip install poetry # or pip install -r requirements.txt
    - poetry install --no-root # or pip install -r requirements.txt
  script:
    - pytest

deploy_prod:
  stage: deploy
  script:
    - docker login -u "$DOCKER_REGISTRY_USER" -p "$DOCKER_REGISTRY_PASSWORD" $CI_REGISTRY
    - docker push $DOCKER_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
    - # Here could be commands to SSH into the production server, pull the new image, and restart the service.
    - # For example: ssh user@prod "docker pull $DOCKER_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA && docker-compose up -d"
  only:
    - main

This saves me 5-10 minutes of setup time, and I can dedicate that time to defining my SLOs (Service Level Objectives) or thinking about more complex feature flag strategies. I frequently use AI to generate simple code snippets for collecting observability metrics, such as Prometheus exporters or Grafana dashboards.

Dependency Syndrome: Knowledge Loss and Critical Thinking

As tempting as productivity gains are, the risk of developing a dependency on AI assistants always lingers in the back of my mind. Once upon a time, when I needed to write a complex Linux command, I would pore over man pages, try different parameters, and ultimately learn every detail of that command when I found a working solution. Now, I ask AI, "Find the 10 largest files in the file system and sort them by size," and I directly copy the output. While this solves my immediate problem, it causes me to miss the opportunity to learn the nuances of fundamental commands like find, du, sort.

This situation can lead to the weakening of our critical thinking and problem-solving muscles. When faced with a VLAN tagging issue, in the past I would analyze tcpdump outputs, check switch configurations, and step-by-step find the root cause. Now, when I ask AI, "How to fix a VLAN tagging issue?", it provides general solutions. However, subtle details like MTU/MSS mismatch or BGP configuration errors causing routing flaps can only be understood through in-depth investigation. AI cannot yet provide this depth of analysis, so losing our fundamental knowledge could leave us helpless in the face of more complex problems.

🔥 Dependency Trap: Lost Fundamental Knowledge

Over-reliance on AI can dull fundamental system administration skills. For example, if you need to free up space on a file system, AI can quickly give you a du -sh * | sort -rh | head -n 10 command. However, using this command without understanding how it affects hard links, how it handles sparse files, or how its performance changes on different file systems (e.g., NFS or Ceph) can lead to different problems later on.

Once, when I was setting the cgroup memory.high limit, I proceeded based on an example given by AI. However, because AI didn't fully explain how soft limits work and the scenarios in which the OOM killer would be triggered, the application didn't behave as expected and continued to overuse memory. I had to manually optimize the systemd unit settings by examining my own journald logs. This reminded me that AI is just a tool, and in-depth system knowledge is indispensable.

Effective Working Methods with AI Assistants: Prompt Engineering and Verification

To get the most out of AI code assistants, you need to treat them like a "junior assistant." That is, you should always keep them under supervision, verify their output, and most importantly, guide them by asking the right questions. This requires a skill called prompt engineering. The more specific and contextually rich a prompt you provide, the better results you will get.

In my own experience, instead of directly telling the AI "write this for me," it has been more productive to use it in the form of "in this context, with these requirements, list possible solutions and explain the trade-offs of each." For example, when I have a performance issue in PostgreSQL, instead of directly asking AI "tell me how to speed it up," I ask, "the EXPLAIN ANALYZE output for this query is this, the table schema is this, the indexes are these. What are the potential optimization strategies, which of B-tree, GIN, or BRIN indexes might be more suitable and why?" This way, I learn not only a solution from AI but also the advantages and disadvantages of different approaches.

ℹ️ Example of Effective Prompt Engineering

Detail your prompts to get more efficient and secure code from AI. For example, if you want to create a rate limiting mechanism in a FastAPI application:

Weak Prompt: "Add rate limiting to FastAPI."

Effective Prompt: "I want to implement user-based rate limiting for my FastAPI application. How can I implement a limit of 10 requests per minute based on IP address or user_id from a JWT token using Redis? What are the scalability and security trade-offs of this solution? Please explain with Python code and Redis commands."

This detailed prompt enables AI to provide you with a much more specific, secure, and explanatory solution. I can even include JWT validation with OAuth2 patterns in this prompt. In the backend of my Android spam blocker application, I significantly sped up the design of rate limiting and DoS mitigation layers with such detailed prompts. I combine the solutions offered by AI with my own CVE tracking and kernel module blacklist experiences to put them through security checks.

Future Perspective: Evolution of the Developer Role

AI code assistants will establish a permanent place in the software development world. This will inevitably lead to the evolution of the developer's role. Instead of merely being a "coder," we will increasingly become "system architects" or "problem solvers." While AI generates boilerplate code, simple functions, or even basic microservice skeletons, we will focus more on high-level architectural decisions, complex business logic integrations, and the implementation of advanced patterns like event-sourcing or CQRS.

My experiences in production ERP have shown that software architecture is mostly about organizational flow, not just software. AI can code this flow, but designing the flow, understanding the needs of different departments, correctly using transaction outbox patterns, or analyzing the effects of eventual consistency on business processes will still require human intelligence. Deciding whether to use logical replication or physical replication in PostgreSQL, determining read replica routing strategies, or optimizing partition strategies based on workload are areas that AI cannot yet fully undertake.

In the future, as developers, we must learn to use AI as a "co-pilot." This means not just copying and pasting the code AI generates, but also finding its errors, identifying its weaknesses, and guiding it to produce better solutions. This evolution will direct us towards more creative, strategic, and higher value-added work. Perhaps one day AI will flawlessly generate auditd rules or SELinux/AppArmor profiles for us, but understanding the real-world implications and edge cases of these profiles will still fall to us.

Conclusion: A Balanced Approach Between Productivity and Dependency

The productivity gains that AI code assistants bring to our software development processes are undeniable. They save us time in daily repetitive tasks, help us adapt to new technologies faster, and accelerate prototyping processes. While working on the AI application architecture for my own side products, I see how critical prompt engineering and RAG patterns are. I even aim to get more reliable answers from AI with multi-provider fallback strategies like Gemini Flash, Groq, and Cerebras.

However, we must not ignore the potential risk of dependency on these tools and their possibility of dulling our critical thinking abilities. My clear position is to use AI as a tool, not as an authority. Questioning every output, verifying it, and maintaining a solid foundational knowledge becomes the most important responsibility of a modern developer. AI can give us speed, but in-depth expertise and problem-solving skills are still our most valuable assets. As long as we maintain this balance, AI code assistants will truly serve as a productivity engine.

Top comments (0)