megacolorboy

Abdush Shakoor's Weblog

Writings, experiments & ideas.

Rewriting my SSG again — The right way

Around eight years ago, I wrote my own static site generator in Python. It was very simple. No frameworks, no strong structure — just scripts that generated pages for my blog.

It worked, so I kept using it.

There were no proper validations. No structured exception handling. Almost no separation of concerns. I didn’t follow standard practices. At that time, it was just a hobby project, and I only cared that it produced HTML files correctly.

And it did.

To be honest, I was too lazy to rewrite it and it was "stable" enough, so I ignored the technical debt.

Contrasting differences

Over the years, I’ve worked on much larger and more structured systems — enterprise APIs, background services, integrations, layered architectures. I’ve learned to care about validation, clean boundaries, proper error handling, and maintainability.

Now when I open my old SSG code, I can clearly see the difference.

It reflects how I used to think about code.

It’s not terrible. It’s just basic. It lacks discipline.

There’s no validation layer. No clear contracts. Some modules handle too many responsibilities. Dependencies are outdated. Type hints are either missing or inconsistent.

The real issue is that it works — but extending it doesn’t feel comfortable.

Why I decided to rewrite it?

This rewrite is not about fixing bugs. There are no critical issues. It’s just that single god-like python class is really bugging me.

I want to:

  • Add proper validation
  • Introduce structured exception handling
  • Strengthen type hints
  • Upgrade dependencies
  • Separate responsibilities clearly
  • Make it easier to extend in the future

I also want to refresh my blog properly. If I’m going to continue building on top of this tool, it should be something I trust and feel comfortable maintaining.

Right now, adding a new feature feels like touching something fragile. After refactoring, I want it to feel stable and predictable.

Time for some justice!

This time, I’m approaching it like a real project, even though it’s still personal.

Clear module boundaries. Explicit data models. Proper validation. Clean CLI entry points. Better organization overall.

I’ll be using PyCharm heavily during this process. When renaming models, reorganizing modules, or tightening type contracts, I want safe refactoring and immediate feedback. Strong inspections and accurate find usages will help a lot when reshaping older code.

Tooling becomes more important during refactoring than during initial development.

I’m not trying to over-engineer it. I’m just trying to build it properly — based on what I’ve learned over the years.

Conclusion

Sometimes old code is not a mistake. It’s just a snapshot of your earlier experience.

Rewriting this SSG is simply updating it to match how I think today.

Over the next few days, I’ll be cleaning it up and rebuilding it with better structure. Not because it failed — but because I’ve improved.

Hope you liked reading this article!

How JetBrains IDEs Improved My Productivity Across .NET, Laravel, and Python

I work across three different stacks almost every week: .NET for enterprise APIs and integrations, Laravel for large backend systems, and Python for CLI tools and automation.

On paper, these ecosystems are very different. Different communities, different tooling culture, different philosophies.

But my development experience feels almost the same every day — because I use JetBrains IDEs for all of them.

Rider, PHPStorm, and PyCharm share almost identical core capabilities:

  • Smart navigation
  • Safe refactoring
  • Powerful debugging
  • Git integration
  • Database tools
  • Deep code inspections

This is not about which IDE has more features, it’s about staying in that flow of productivity.

Being in the flow

What I like about the JetBrains suite is that, when I move from .NET to Laravel to Python, I don’t feel like I switched tools. I only switched languages and frameworks.

That small difference matters more than people think.

As developers, we already deal with architecture decisions, business rules, integrations, performance issues, and production risks. When I change stacks, I don’t want to also change keyboard shortcuts, debugger behavior, navigation style, or refactoring workflow.

With JetBrains, the mental model stays stable and reduces my mental friction in a very real way.

No more refactoring nightmares!

I might be slightly opinionated but hear me out, okay?

If you work on serious systems — payments, integrations, background jobs, multi-layer architectures — you cannot afford sloppy refactoring.

I frequently:

  • Move logic from controllers into services
  • Rename DTO properties
  • Break large files into smaller components
  • Extract interfaces
  • Clean legacy code

Making use of features like: Search Everywhere, Go to definition, Find usages or Refactor has significantly improved my code confidence and that allows me to continuously improve architecture instead of being afraid to touch old code.

Debugging feels calm and predictable

Whether I’m debugging:

  • A controller in .NET
  • A service in Laravel
  • A module in Python

The experience is consistent and predictable: breakpoints, variable inspection, stepping into async calls, evaluating expressions, you name it!

When debugging feels predictable, solving complex problems becomes less stressful. And that directly improves productivity.

The IDE as a Second Reviewer

Another thing I value is how deeply the IDE understands the code.

It doesn’t just highlight syntax errors. It understands type relationships, method references, incorrect imports, potential null issues, and unused dependencies.

Many mistakes are caught before I even run the application.

It feels like having a second reviewer sitting beside me while I write code.

For someone working across multiple stacks, that safety net is quite powerful.

A consistent ecosystem

The biggest advantage, however, is consistency.

I don’t want three different mental environments. Rather something that adapts to the language. With Rider, PHPStorm, and PyCharm: The engine, navigation and philosophy feels the same.

I mean, don't get me wrong, I still use VS Code, Visual Studio, and lighter setups. They are really good tools in their own way.

But when working on enterprise APIs, government systems, payment workflows, and long-running background processes, I prefer:

  • Depth over minimalism
  • Strong refactoring over quick editing
  • Intelligent tooling over lightweight flexibility

Productivity is rarely about one big feature. It’s about small improvements repeated every day:

  • Strong Git integration
  • Built-in database tools
  • Consistent formatting
  • Reliable search
  • Clean UI scaling on high-resolution screens

Each one saves seconds. Over months and years, those seconds become hours.

Final Thoughts

By the time, you're done reading this article, one might assume that working across multiple tech stacks .NET, Laravel, and Python could feel chaotic.

For me, I feel like I've found the right ecosystem that just doesn't get in my way and allows me to be consistent, productive and focused on building stuff and problem-solving instead of fighting my tools.

Consistency is one of the most underrated productivity multipliers in software development.

Hope you liked reading this article!

Can AI really replace software engineers?

My take on the impact of AI in the near future for software engineers.

I’ve spoken about this topic on this blog two years ago. But with how dramatically the AI landscape has changed—especially with the advent of more advanced models—I think it’s worth revisiting.

Think about it: if companies like OpenAI, Anthropic, or Microsoft truly believed that AI could replace software engineers, why would they still aggressively hunt for top engineering talent in Silicon Valley or spend billions acquiring startups?

Task or Responsibility?

Here’s how I see it in this AI era: AI can replace many programming tasks, but not the role or responsibility itself.

Programming is only one part of the job. If you step back and think about what you actually do, you’ll realize there’s a lot more involved than just writing code in your favorite editor.

This is where many people go wrong—by conflating a task with the role. It’s similar to saying calculators replaced mathematicians or accountants. Yes, calculators automated arithmetic, but they also enabled people to focus on more complex problems. Arithmetic was never the job; understanding the principles behind it was.

AI works the same way. It makes execution faster, but it doesn’t replace understanding.

What AI can’t do?

Think about what you actually do in a typical week.

You sit in closed rooms with project managers and clients who describe vague or unintelligible problems. You’re the one who decodes what they actually need. You look at the codebase and:

  • Figure out which parts need to change and which must remain untouched
  • Push back on feature requests that might introduce long-term technical debt
  • Review a colleague’s code before it reaches production
  • Decide whether something is ready to go live or needs more testing

There are many more responsibilities like this—and none of them are programming.

It’s just your job.

Raising concerns

This post isn’t meant to turn a blind eye to what’s happening in the industry.

We’ve seen massive layoffs across large corporations and companies reducing headcount. Will this happen again? Absolutely. But in most cases, these are cost-cutting measures wrapped in a different narrative, with AI often used as a convenient justification.

So who stays, and who’s at risk?

Engineers who understand that their role goes far beyond writing code—those who bring context, judgment, and clarity to ambiguous problems—are far more likely to remain valuable. On the other hand, those who rely solely on producing output without understanding why they’re producing it are the most vulnerable.

A stronger feedback loop

Will junior engineers be replaced? That’s something I plan to address in a separate post.

But one thing worth discussing is this: if AI handles a large part of code generation, can juniors still build judgment? I think they can—because AI significantly shortens the feedback loop.

Having spent over a decade in this industry, I remember the days of endlessly browsing Stack Overflow and flipping through programming books for answers. What once took hours or days now takes seconds. It may feel like skipping steps, but in reality, you’re just learning faster.

Consider this: you were hired before the AI wave because your company saw value in what you brought to the table. Now, with AI tooling, you’re significantly more productive. You ship faster, handle more complex scenarios, and deliver better outcomes.

It wouldn’t make much sense for a company to let you go simply because you’ve become more efficient at your job.

Staying ahead

If you’re already thinking about how to adapt, here’s where you can start:

  • Use AI tools: Whether it’s Claude, ChatGPT, Cursor, or something else—figure out what works for you and what doesn’t
  • Strengthen your actual role: Focus on understanding requirements, trade-offs, and communication with stakeholders
  • Learn systems end-to-end: The more you understand how a system works as a whole, the harder you are to replace
  • Document your work: Keep track of how you solve problems—it pays off later in your career
  • Stay open to learning: Being defensive or closed-minded will only slow you down. Embrace the tools and move forward

Conclusion

This field is changing rapidly. Tasks that once took days can now be completed in seconds. Some skills are becoming less relevant, while others are more important than ever.

If there’s one thing to take away from this, it’s this: your value was never in writing code. It’s in knowing what to build, why to build it, when to ship, and when to push back. It’s about solving the right problems—problems that actually help people.

9 Software Books I'm Reading This Year to Become a Better Engineer

As a software engineer, continuous learning is essential. The tech industry evolves rapidly, and keeping up requires a commitment to improving skills, refining best practices, and understanding the deeper principles behind great software design. This year, I’ve curated a list of nine must-read books that will help me become a better developer, architect, and problem solver.

If you're looking to level up your software engineering skills, these books might interest you too:

The Pragmatic Programmer

By Andrew Hunt & David Thomas

"Care about the code you write and the people who will maintain it after you."

A foundational book that every developer should read. It provides practical advice on coding, debugging, and automation, making you a more adaptable and efficient engineer.

Designing Data-Intensive Applications

By Martin Kleppmann

"A well-designed system grows gracefully as the dataset, traffic volume, or complexity increases."

A must-read for backend engineers and system architects, this book explores data modeling, distributed systems, and scalability challenges.

Clean Code

By Robert C. Martin

"Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code."

Writing clean, maintainable code is a core skill for developers. This book offers practical techniques for improving code quality.

The Mythical Man-Month

By Frederick P. Brooks Jr.

"Adding manpower to a late software project makes it later."

A classic in software project management, this book explains why adding more people to a late project only makes it later.

Refactoring

By Martin Fowler

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."

A practical guide to improving existing code without altering its functionality, making it more readable and maintainable.

Domain-Driven Design

By Eric Evans

"The heart of software is its ability to solve domain-related problems for users."

This book teaches how to align software design with business logic, making complex software systems more manageable.

Working Effectively with Legacy Code

By Michael Feathers

"To change software means to change behavior. Our goal is to do so safely."

If you’ve ever had to deal with old, complex codebases, this book provides strategies for refactoring without breaking functionality.

Why Programs Fail

By Andreas Zeller

"Every bug has a cause, and every cause has a cure."

A systematic approach to debugging, helping developers diagnose and fix software defects effectively.

Extreme Ownership

By Jocko Willink & Leif Babin

"Leaders must own everything in their world. There is no one else to blame."

Leadership and accountability are critical for engineers working in teams. This book teaches how taking full responsibility leads to success.

Final Thoughts

These books cover a wide spectrum of skills—from writing clean code and designing scalable systems to debugging, refactoring, and leadership. By reading and applying the lessons in these books, I aim to become a better software engineer this year.

Laravel Scopes vs. Builder Queries: Which Should You Use?

If you're building a Laravel application, you're probably spending a lot of time writing queries. And as your project grows, you'll inevitably face this question: Should I use scopes or builder queries? While both have their place, choosing the right tool for the job can make a world of difference. Here's my opinionated take on the matter.

The Case for Scopes

Scopes are, quite simply, one of Laravel's hidden gems. They let you encapsulate common query logic within your models, making your code clean, reusable, and easy to read. Think of them as tiny, purposeful functions designed to save you time and sanity.

Take this example:

<?php
    // In your model
    public function scopeActive($query)
    {
        return $query->where('status', 'active');
    }

    // Usage
    $activeUsers = User::active()->get();
?>

Suddenly, instead of littering your controllers with where('status', 'active') everywhere, you have a single, reusable method that reads like English. Scopes shine when you need commonly used filters like active, published, or recent. They’re easy to use, they’re consistent, and they make your code feel more intuitive.

Why I Prefer Scopes for Reusability

Here’s the thing: in any sizable Laravel app, you’ll inevitably find patterns in your queries. Rewriting the same query logic over and over? That’s a code smell. By using scopes, you centralize your query logic, reducing redundancy and improving maintainability.

For example:

<?php
    $pubishedPosts = Post::published()->recent()->get();
?>

This reads beautifully and keeps your codebase DRY (Don’t Repeat Yourself). If the definition of "published" or "recent" changes, you only need to update it in one place. Scopes turn repetitive query logic into single lines of magic.

The Case for Builder Queries

That said, not everything belongs in a scope. Some queries are just too specific, too complex, or too dynamic. This is where builder queries come in.

Imagine you’re building a report that requires multiple joins, conditional logic, or dynamic filters. Scopes could become unwieldy here. Instead, a well-crafted builder query in your controller, service, or repository might make more sense:

<?php
    $users = User::where('status', 'active')
        ->whereDate('created_at', '>', now()->subDays(30))
        ->orderBy('created_at', 'desc')
        ->get();
?>

Builder queries are perfect for:

  • One-off operations.
  • Highly dynamic queries.
  • Scenarios where scopes would make your models bloated or overly complex.

The flexibility of builder queries is unmatched. You can construct them on the fly, adapt them to user inputs, and handle edge cases without worrying about making your models an unreadable mess.

My Opinionated Take: Use Scopes as a Default, Builder Queries for the Edge Cases

If I had to pick a side, I’d say: lean on scopes as your default tool, and reserve builder queries for those rare cases when scopes just don’t cut it. Why?

  1. Scopes enhance readability. Your queries read like sentences, and your intentions are crystal clear.
  2. Scopes promote DRY principles. They’re reusable and encapsulate logic, which makes future maintenance a breeze.
  3. Builder queries are powerful but can become messy. Unless you’re careful, a complex query in your controller can grow into a sprawling monstrosity. Keep your controllers lean and delegate to scopes or dedicated query classes where possible.

When Not to Use Scopes

There are times when using a scope might do more harm than good:

  • Too much complexity: If a scope needs multiple parameters or involves complex joins, it’s better suited as a custom query builder or a dedicated repository method.
  • Rarely used logic: Don’t clutter your models with scopes for queries that are only needed once or twice.
  • Dynamic, user-driven queries: When filters are highly variable, builder queries give you the flexibility you need.

Conclusion: Balance Is Key

Laravel gives you powerful tools to write queries, and both scopes and builder queries have their roles. Use scopes to simplify and centralize reusable logic, and reach for builder queries when flexibility and complexity demand it. By balancing both, you’ll keep your codebase clean, maintainable, and a joy to work with.

So, what’s your take? Are you a scope enthusiast or a builder query champion? Either way, Laravel’s got you covered.

A Starter Guide to Software Version Control

Version Control Systems (VCS) are indispensable tools for modern software development. When I first started working with version control, I was overwhelmed by the terminology and the various workflows. Over time, I realized that mastering VCS isn't just about understanding commands—it's about adopting practices that make development smoother and more collaborative. In this post, I’ll share my journey, the lessons I’ve learned, and a practical approach to version control that works for teams of any size.

What is Version Control?

I remember my first experience losing hours of work because I accidentally overwrote a file. That’s when I discovered the magic of version control. It’s like having a time machine for your code! At its core, version control tracks and manages changes to your software code. Here’s why it’s essential:

  • Collaborate with ease: Gone are the days of emailing files back and forth. Multiple developers can work on the same codebase without stepping on each other’s toes.
  • Track every change: With a detailed history of changes, debugging becomes less of a nightmare.
  • Rollback anytime: If something breaks, you can revert to a stable version in seconds.

Today, tools like Git, Subversion (SVN), and Mercurial are popular choices, with Git leading the pack. If you haven’t tried Git yet, you’re missing out on a developer’s best friend.

Key Concepts

Here’s a quick glossary that helped me when I was starting out:

  1. Repository: Think of this as your project’s home. It stores your code and its entire version history.
  2. Commit: A snapshot of your code changes. A good commit is like leaving breadcrumbs for your future self or teammates.
  3. Branch: This was a game-changer for me. Branches let you work on new features or fixes without touching the main codebase.
  4. Merge: When you’re ready to bring your work back into the main project, merging combines it with the existing code.
  5. Tag: These are like bookmarks, marking specific points in your project’s history—perfect for releases.

Types of Branches

When I joined my first team project, I was introduced to a branching strategy that revolutionized the way I worked. Here’s how it breaks down:

1. Main Branches

  • Main (main or master): This is the crown jewel—the stable, production-ready branch. It’s sacred territory where only thoroughly tested code belongs.
  • Development (dev): This is where the magic happens. New features and fixes are integrated and tested here before they’re ready for production.

2. Feature Branches

When you’re working on something new, create a feature branch. Here’s how it works:

  • Purpose: To develop a specific feature.
  • Workflow: Start from dev, and when you’re done, merge back into dev.
  • Naming Convention: feature/new-login-system

3. Release Branches

Preparing for a new release? Here’s what you do:

  • Purpose: To finalize a version for production.
  • Workflow: Start from dev, do final testing and fixes, then merge into main.
  • Naming Convention: release/v1.0

4. Hotfix Branches

Production bugs can’t wait. That’s where hotfix branches save the day:

  • Purpose: To fix critical issues in production.
  • Workflow: Start from main, fix the issue, then merge into both main and dev.
  • Naming Convention: hotfix/login-bugfix

A Practical Workflow Example

Here’s a typical workflow I follow:

  1. Feature Development: Let’s say I’m building a new login system. I’d create a branch called feature/new-login-system off dev. Once the feature is complete and tested, I merge it back into dev.
  2. Preparing for Release: When it’s time to launch, I’d create a branch called release/v1.0 from dev. After some final tweaks, I merge it into main and tag it as v1.0.
  3. Production Hotfix: If a bug pops up in v1.0, I’d create a branch called hotfix/login-bugfix from main. Once fixed, I’d merge it into both main and dev and tag it as v1.0.1.

Best Practices

Here are some lessons I learned the hard way:

  1. Write Clear Commit Messages: Your future self will thank you. A good message explains what changed and why.
  2. Keep Commits Small: Each commit should represent a single, logical change. This makes debugging a breeze.
  3. Review Before Merging: Always use pull requests or merge requests. Two heads are better than one.
  4. Use Tags for Releases: Tags are lifesavers when you need to rollback or track changes.
  5. Backup Your Repository: This might sound obvious, but don’t take it for granted.

Why Version Control is a Game-Changer?

For me, version control transformed how I approach coding. It made collaboration smoother, debugging faster, and deployments safer. Whether you’re working solo or with a team, adopting a thoughtful version control strategy is a must.

Final Thoughts

Version control is more than just a tool—it’s a mindset. By following the practices and strategies outlined here, you’ll be well on your way to mastering it. Trust me, your future self—and your teammates—will thank you.

Cicada 3301

A short docu-series about one of the most mysterious puzzles on the internet.

Ever heard of Cicada 3301? It's one of those mysterious puzzles that made every crypto enthusiast around the globe turn into super sleuths hunting for clues and chasing the next puzzle. I was quite interested in it after watching this series of documentaries titled Cracking the Code of Cicada 3301 that shot by Great Big Story, which made my interest towards my cryptography grow larger.

Although, I never tried this series of puzzles myself, the actual host of this puzzle is unknown as some claimed that it could be a recruitment programme by the FBI/CIA/NSA/GCHQ to hire super smart individuals or maybe some crypto/security group that cares about data privacy and security.

I remember watching this series back in 2020 and it was quite hard for me to find these videos and hence, I'm sharing it here for you and my reference as well (in case, I wanted to re-watch the documentary again):

What I liked about this documentary series is how it connects people of various disciplines such as Mathematics, Programming, Linguistics and Steganography to deduce and decipher a really complex puzzle. I just find that inspiring because knowledge is power and when aligned with people of similar interests and ambitions, you build new bridges of friendship that spans around the globe.

Hope you liked reading this article.

Don't forget what sparked your passion

A short letter to myself and other fellow programmers.

Do you ever remember falling in love with programming when learning to code during school days or while working on a client's project? Probably not.

When I started learning to code, I never wrote code because I wanted to but because I desired to. No one to poke me around with tight deadlines, no need to race against time. Just the feeling of power and creativity in your hands to build something out of nothing but just pure code. It could have been anything as simple as building a small widget for my blog post, writing a game, solving a problem in LeetCode or Project Euler or maybe writing a bash script to automate my tasks.

You could probably relate to some but for me, these are some of the examples that resonate with me.

I'm writing this as a reminder to myself and other fellow programmers: don't ever forget that moment when your passion for programming had sparked. You might be older now and you're probably filled with a lot of responsibilities (especially if you're parenting).

Perhaps, you might never even experience that same amount of adrenaline and rush that you had when you were behind the computer in your room while your siblings and friends were trying to distract you.

There's never an end to learning. You can still find some passion projects that'll help you keep that spark. Have an open mind and be open to newer ideas. Solve a puzzle, read a book, write a game or maybe a blog post.

Don't do this because you need to impress someone or for the sake of it rather because you can and it brings you joy.

Oh and wishing you a Happy New Year! 😀🎄