Skip to main content

Command Palette

Search for a command to run...

Ditch vibe coding and build developer instincts

Don't outsource your boring work

Updated
7 min read
Ditch vibe coding and build developer instincts
T

Software engineer transitioning into ML/AI Engineering

TL;DR

  • AI is super helpful, but it can’t think for you:
    Vibe coding is chill and comfy but don't miss out on developing good intuition for good code

  • Your instincts are built in the boring stuff:
    Catching weird layout bugs or avoiding spaghetti code happens when you wrestle with the little annoying things—don’t skip them.

  • Let AI assist, not autopilot:
    Sure, AI can speed things up, but always review and refactor. And remember: it’s only as smart as the person guiding it.

Introduction

I've been hearing about the “vibe coding” trend for some time now and am very skeptical about it. Before we begin, this is the tweet from which the term seems to have originated:

I'm not sure exactly what he means by “embrace exponentials” and at this point, I'm too afraid to ask but I feel the whole thing boils down to this line:

“I'm building a project or web app, but it's not really coding - I just see stuff, say stuff, run stuff, and copy-paste stuff, and it mostly works”.

Now - I won't lie. I'm a bit guilty here as well. Coding with good AI assistants is just sooo pleasant. Turning on Claude Sonnet and seeing it implement features I'd probably be bored to work on is just… calming. I don't really want to deal with internationalization, favicons, and thinking about the syntax to implement simple features that still require some manual work, so it's just really comfortable to let someone take over while I watch.

There's a catch here though, especially for people just starting in software.

Developer instincts.

This is what you're missing out on when you outsource your boring, manual, unpleasant work to AI if you do it all the time. You're not building your developer instincts. Software development is much more than just code, and this applies everywhere. From an outside perspective, it's obvious Google-like scale requires significant engineering planning and principles in addition to actual code, but the vast majority of people starting in software won't see an environment like that early in their careers.

So the only place you can start developing your instincts… is in the projects you have access to. That internal tool you take care of in your job, and also that pet project you do for fun on the weekends. And guess what? Regardless of how trivial they might seem, they're also a good opportunity to develop your spider sense towards bad smells and poor code architecture.

Developer instincts in real life

To put what I have to say in context, this is one of the side projects I'm working on: Implementing the layout below for the web and mobile. This is the layout for the Endopolitica website, a women's health initiative I and my wife are working on (do you know anyone with endometriosis? Follow us here)

Design by Sofia Oliveira for Endopolítica, all rights reserved.

If you noticed in the layout above, we're using a mixture of vectors and “normal” images for each section. While this is beautiful from a design perspective, it incurs an engineering contradiction. Normal images won't upsize and downsize as well as vectorized ones, so we need to ensure they're two separate images that scale differently but look like one to the user. So we encapsulate the vectorized section as a background wrapper and the main image as a component on top of it. This is what it looks like in the code:

  const imageContent = leadingImage || background ? (
    <ImageContainer item xs={12} md={6}>
      <BackgroundWrapper sx={{ margin: margin }}>
        {background}
      </BackgroundWrapper>
      {leadingImage && (
        <LeadingImage
          sx={{
            frontImage: `url("${leadingImage}")`,
            height: leadingImageHeight || "100%",
            backgroundPosition: backgroundPosition,
            ...(backgroundDisplay && { display: backgroundDisplay }),
          }}
        />
      )}
    </ImageContainer>
  ) : null;

Then we pass in background and leadingImage arguments for each page section:

        {/* Manifesto Section */}
        <ContentSection 
          id="manifesto"
          title={t.manifesto.title}
          content={t.manifesto.content}
          backgroundColor="orange"
          background={<ManifestoBackground />}
          leadingImage="/manifesto.png"
          leadingImageHeight="90%"
          backgroundPosition={{ 
            xs: "bottom center",
            md: "bottom center"
          }}
          margin="0 0 0 2rem"
        />

That code worked fine for all components but one:

The texture side (<ManifestoBackground /> in the code) wouldn't fill up the whole height of its parent container. This hadn't been an issue with the other components. Take this one for example:

It's very similar in structure to the orange one. It also has text first, image later, and a vectorized background wrapper with a leading image on top of it. We don't apply specific styling to this one, so we shouldn't need it for the other. I asked Claude Sonnet to generate a fix for the issue. It gave me plenty of suggestions. All of which involved refactoring significant portions of the code, and most of the time introducing more complexity than it was worth.

For example, this is one of the suggestions I received for solving the orange container issue.

// Inside your ContentSection component, add the function:
const getImageContainerStyle = () => {
  if (backgroundColor === "orange") { // Specific styling for the orange section
    return { 
      paddingTop: "0",
      paddingBottom: "0",
      paddingLeft: "0", 
      paddingRight: "0",
    };
  return {}; // Default styling
};

// Then in the JSX where you render the imageContent:
return (
  <Box id={id} sx={{ my: 4 }}>
    <CardComponent {...cardProps}>
      <Grid container spacing={4}>
        {imageFirst ? (
          <>
            <Box sx={getImageContainerStyle()}>
              {imageContent}
            </Box>
            {textContent}
          </>
        ) : (
          <>
            {textContent}
            <Box sx={getImageContainerStyle()}>
              {imageContent}
            </Box>
          </>
        )}
      </Grid>
    </CardComponent>
  </Box>
);

At a glance, this seems like a valid solution. It extracts away the responsibility of setting each section's styling to a separate getImageContainerStyle() method, which would determine which style to apply based on the background color of each card. What this solution doesn't consider though is that we already have a portion of the code that implements a very similar logic:

const SectionCard = styled('div')({
  borderRadius: "12px",
  padding: "32px",
  marginBottom: "32px",
  position: "relative",
  overflow: "hidden",
});

const OrangeCard = styled(SectionCard)(({ theme }) => ({
  backgroundColor: "#FFA000",
}));

const PinkCard = styled(SectionCard)(({ theme }) => ({
  backgroundColor: "#f284ab",
}));

const PurpleCard = styled(SectionCard)(({ theme }) => ({
  backgroundColor: "#DD89FF",
  paddingRight: "0px",
}));

Applying styles directly to elements elsewhere would create a duplicate source of truth for these components. While this would already be suboptimal for a small project like this, imagine we were developing a design system for a large company with hundreds, if not thousands, of different cards and colors, among other things. It would quickly become unmaintainable.

People are already finding this out:

“It made me realize: AI is an amazing tool, but it won’t replace proper planning. If I had mapped out my architecture first, set clear rules for structure, and used AI as an assistant instead of a crutch*, I wouldn’t be in this mess now.*

So now, I’m doing what I should have done from the start: cleaning up, refactoring, and actually learning to structure my code properly*. If you’re using AI to write code, my advice?* Think first, generate second. And if you're knee-deep in AI-generated spaghetti code like me, it might be time to step back and rethink your approach.”

What I find somewhat funny about this particular problem is that Claude never considered a non-code-based solution. In the end, there was a fix that didn't introduce new code or increase the project's complexity. The issue was that the SVG file for the orange background section was marginally different from the others.

You see how textura-manifesto-2.svg doesn't fill up the whole height of the image it's inserted in? Yep, that was causing the issue. The solution then was super simple. I edited the SVG so it'd fill up the whole image and vois la, problem solved:

What worries me deeply here is that at no point did Claude consider that the issue could have been something not related to the code itself, and even when it did it suggested an approach that seemed reasonable at first glance but that could turn out to be a terrible choice soon.

Once again, my developer instinct saved me from introducing bad code to my codebase and prevented issues later. However, I wouldn't have developed it if, in the early stages of my learning, I had delegated small, non-sexy issues like this to a third party and just followed its suggestions mindlessly.

Conclusion

Of course, coding with AI isn't all bad. In its merit, Claude gave many, many good suggestions for this project. It helped me build a good-looking responsive navbar quickly, and it also helped me refactor the code to support multiple languages very fast. Neither of those generated bad code, so once I reviewed it I was able to incorporate those changes to the code mostly untouched. But this is possibly the most worrisome aspect of all of this. For every ten good suggestions it gives, one or two will introduce bad smells that could cost highly down the line.

I'll continue using AI assistants while coding, for sure, but I'm also putting in double the effort while reviewing the code it generates. More importantly, I'll make sure to treat AI assistants for what they are: Assistants. And a good assistant can only go as far as the pilot guiding it. So let's all make sure we're putting in the hours of study and manual work it takes to be as good of a pilot as possible.