refactoring post
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
Michael Zhang 2024-06-21 00:57:37 -05:00
parent 435ec21f6e
commit 5d631561b5
4 changed files with 119 additions and 51 deletions

58
flake.lock Normal file
View file

@ -0,0 +1,58 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"id": "flake-utils",
"type": "indirect"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1718089647,
"narHash": "sha256-COO4Xk2EzlZ3x9KCiJildlAA6cYDSPlnY8ms7pKl2Iw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f7207adcc68d9cafa29e3cd252a18743ae512c6a",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View file

@ -1,51 +0,0 @@
---
title: Coping with refactoring
date: 2023-09-02T02:17:23.405Z
tags:
- software-engineering
heroImage: ./ruinsHero.png
heroAlt: ruins
draft: true
---
It is the inevitable nature of code to be refactored. How do we make it a less
painful process?
It pains me to start a stream-of-consciousness type of article with a
definition, but to set things straight let's be sure we're talking about the
same thing. When I say **refactoring** I mean changing potentially large parts
of the codebase purely for the sake of making it more "organized". For some
definition of organized.
As software developers, we usually think of refactoring as something we do in
order to make something easier. A common example would be something like a few
lines of code that people on your team have just been copy and pasting
mindlessly everywhere, because to make it generic would mean that they would
have to touch code outside of their little bubble and then reviewers get
hesitant at the diffs and yadda yadda all kinds of problems supposedly.
Now it's your turn, and you have to change something tiny in those few lines of
code ...everywhere. Before you go in and start abstracting all of it into
something more generic, take a breather and think for a second: is it worth it
to refactor?
If your refactor involves adding some extra helper classes or you're pulling out
your toolbelt of design patterns, **you are creating complexity**. And in the
software world, complexity is the real devil.
Many people try to code in an "extensible" way in order to avoid refactors, with
extravagant interfaces and inheritance patterns. But all they've created is just
a larger mess that's harder to clean up later down the line when it eventually
needs to be rewritten. And it _will_ eventually need to be rewritten.
Let's talk about object-oriented programming. There's this bizarre
[open-to-extension but closed-to-modification][1] principle I've observed where
people are so resistant to changing their source code that they'd implement
heaps of useless design patterns on top of it in order to keep their little
classes from ever being touched.
[1]: https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle
If that doesn't sound insane to you, let's take a look at a case study. Suppose
you're writing some code that takes in a request type,

View file

@ -0,0 +1,61 @@
---
title: Coping with refactoring
date: 2024-06-21T05:56:50.594Z
tags:
- software-engineering
heroImage: ./ruinsHero.png
heroAlt: ruins
---
It is the inevitable nature of code to be refactored. How do we make it a less
painful process?
A not-horrible approach to creating a piece of software by first developing the
happy path, and then adding extra code to handle other cases. When we do this,
we may find that patterns emerge and some parts may be abstracted out to make
the code cleaner to read. This makes sense.
It seems that many engineers decided that this process of abstracting is too
painful and started using other people's abstractions pre-emptively in order to
avoid having to make a lot of code changes. They may introduce patterns like the
ones described in the GoF Design Patterns book.
Some abstractions may be simple to understand. But more often, they almost
always make the code longer and more complex. And sometimes, as a part of this
crystal ball future-proofing of the code, you may make a mistake :scream:. At
some point, you will have to change a lot more code than you would've had to if
you didn't start trying to make a complex design to begin with. It's the exact
same concept as the adage about [premature optimization][2].
[2]: https://en.wikipedia.org/wiki/Program_optimization
As an example, as a part of one of my previous jobs, I was reviewing code that
created _10+ classes_ that included strategy patterns and interfaces. The code
was meant to be generic over something that could be 1 of 4 possibilities. But
the 4 possibilities would basically never change. The entire setup could've been
replaced with a single file with a 4-part if/else statement.
I'm not saying that design patterns aren't useful. If we had more possibilities,
or needed to make it so that programmers outside our team had to be able to
introduce their own options, then we would have to rethink the design. But
changing an if statement in a single file is trivial. Changing 10+ files and all
the places that might've accidentally referenced them is not.
Some people think they can dodge the need to refactor by just piling more
abstractions on top, in a philosophy known as ["Open to extension, closed to
modification."][1] I think this is just a different, more expensive form of
refactoring. Increasing the number of objects just increases the amount of code
you need to change in the future should requirements or assumptions change.
[1]: https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle
So the next time you're thinking of introducing design patterns and creating a
boat load of files to hide your potential complexity into, consider whether the
cost of adding that abstraction is worth the pain it will take to change it
later.
> [!NOTE]
> As a bonus, if your language has a good enough type system, you probably don't
> need the strategy pattern at all. Just create a function signature and pass
functions as values!

View file

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB