OK, let's dive straight in. We're picking U with a really
foundational refactoring strategy.
Extract class. This isn't just tightening U
right? It's about really clarifying
responsibilities when a class starts getting, well, bloated.
Exactly. Bloated is a good word for it.
You see a class accumulating way too many attributes, maybe
handling too many different jobs.
That's the signal. It often points to a violation
of the the Single Responsibility principle.
Right. One class, one job.
Pretty much. So if you spot attributes or
responsibilities within that class that kind of feel like
they belong together, like they form their own little group,
extract classes. OK, pull that group out.
Create a new class for them. And the original class then just
holds an instance of that new class.
Precisely. It gets an attribute of this new
type you just created. It delegates the work.
It cleans up the boundaries, makes things much more logical.
The book uses a good example, the Person class holding all the
phone details directly like area code, number, alternative area
code all stuffed into Person. Yeah, that's a classic.
Initially it might seem OK, maybe convenient even.
But pretty quickly that person class starts knowing way too
much about phones. It's less focused.
So the fix is. You create a new Phone class, it
holds the area code and the number.
Then back in the Person class, instead of all those individual
phone fields, you just have say a home Phone object and maybe a
work phone object. Both are of type Phone.
Oh OK, so person doesn't care how the phone number is
structured anymore. Exactly.
It just knows it has phones. It improves modularity
massively, the codes intent is way clearer and responsibilities
are split up properly. Makes reasoning about each part
much easier. And you know this idea connects
to extract interface to similar principal different mechanism.
Like with link list and array list.
Perfect example. They're both list right, but
implemented differently. So you extract a common list
interface. This defines the shared
behaviors, adding items, removing them, getting the size,
stuff like that. And the benefit for, you know,
for us coding against it. The big win is you start
following that prefer interfaces to concrete classes idea.
If your code uses the List interface, you can easily swap
an array list for a link list or something else entirely.
And your code doesn't break. It doesn't care about the
specific implementation, just the list contract.
That's huge for flexibility. So looking at your own code,
this kind of thinking, breaking things down, defining clear
interfaces is really crucial. It stops those huge God like
classes from forming, makes things easier to grasp, easier
to test, and yeah, easier to reuse later on.
Helps you move faster in the long run.
OK, moving on from structure to meaning, let's talk about
renaming. Naming things the famous hard
problem in computer science, right?
Besides cash and validation, of course.
Yeah, the quote holds up. Naming is deceptively difficult.
Why is it so hard and why do we constantly find ourselves
renaming things? Well, there are usually a couple
of reasons. Sometimes, honestly, the first
name just wasn't very good. Not clear, not descriptive
enough. We've all written temp or data
one. Or maybe more often in systems
that live a long time, the thing itself changes, its purpose
evolves. A method initially called
Calculate tax might later also handle applying discounts, so
the name becomes misleading. And a misleading name is just
asking for trouble, misinterpretations, bugs.
Exactly. Good names are vital for
comprehension. They reduce that cognitive load
when someone else, or even you six months later reads the code.
But the tricky part isn't always finding the better name.
It's updating everywhere the old name was used, right?
That sounds risky it. Can be definitely.
Especially in a big system where a method or class is used all
over the place. Miss one reference and boom,
runtime error. It's tedious and error prone.
How do we do it more safely? The book suggests safer approach
using deprecation. Let's say you want to rename
method F to G First you create the new method G You copy the
original code from F into G Then you go back to the old method F
You mark it with at deprecated. That's a special annotation, OK.
What does at deprecated actually do?
It's a signal primarily to the compiler.
In your IDE it says, hey, this thing is outdated, you probably
shouldn't use it anymore. Compilers will usually issue
warnings if someone tries to call the deprecated method F.
So it warns developers and what happens inside F now?
Inside the deprecated method F you just put a single line, a
call to the new method G So F now just delegates to G.
Got it. So old code calling F still
works, but developers get warned and the actual logic lives in G.
Exactly. It's like putting up a detour
sign. The old road still gets you
there for now, but everyone's encouraged to use the new
highway. That makes so much sense.
It doesn't force everyone to update instantly, gives teams
time to adapt. Much safer.
Yeah, it embraces that idea of making changes in small, safe
steps, baby steps, avoids breaking things.
It really reflects a core principle for managing large
software projects. Introduce change gradually
manage the risk, allow for transition.
Makes sense. Stability is key.
Absolutely. Especially when you have complex
dependencies, you minimize disruption.
Then later, once everyone's migrated to G, you can safely
delete F. OK, great strategy.
Now let's shift gears slightly. We've talked big structure
changes like extract glass and crucial clarity changes like
renaming. What about those smaller, more
localized improvements? Things you might do just inside
a single method. Yeah, there are several really
useful ones for that fine green cleanup.
They might seem small, but they add up significantly for
readability and maintainability. Like what?
Well, first up is extract variable.
This is all about simplifying complex expressions.
Imagine you have a long calculation, maybe part of a
formula, all crammed into one line.
It can be hard to read, hard to see the structure.
Right, like nested function calls or complex math.
Exactly. So extract variable says, take a
meaningful chunk of that complex expression, calculate it
separately and assign the result to a new variable with a good
descriptive name. So you break it down, give names
to the intermediate steps. Precisely.
The book might show something like pulling out the BB fourac
ache part of the quadratic formula into a variable called
delta or discriminate. The original formula then
becomes shorter, uses delta, and is immediately easier to
understand. Reduces the mental juggling.
OK, that's clear. Makes sense.
What else? Remove flag.
This one targets those boolean flag variables we sometimes use
to control loops or signal results within a method.
Like setting found in true inside a loop when you find what
you're looking for. That's the classic example.
You loop through something, maybe searching an array.
If you find the item you set, found a true and then maybe
break. After the loop, you check if
found. The refactoring suggests getting
rid of that found variable altogether by using control flow
statements directly. As soon as you find the item
inside the loop, just return true right there.
If the loop finishes without finding it, you return false
after the loop. OK, direct exit.
Yeah, it eliminates the need for the flag variable less state to
track cleaner flow. Often shorter code makes the
exit condition more explicit. Nice.
I like that fewer variables is usually good.
Definitely. Then there's a really powerful
one. Replace conditional with
polymorphism. Oh, OK, polymorphism.
This sounds more involved. It is, but it's fantastic for
cleaning up complex switch statements or long if else if
else chains, especially when the condition is based on the type
of an object. Like checking if student dot
type equals undergrad then else if student dot type master.
Exactly that kind of scenario. The book uses an example like
calculating a student's research grant based on their type
undergrad, master, PhD. Instead of that big switch or if
else block in the code that uses the Student object, the
refactored code becomes incredibly simple.
Maybe just double grant student dot get grant?
Whoa, OK, how does that work? Where did the logic go?
It leverages object oriented programming.
You make get grant an abstract method in a base student class.
Then each specific subclass undergraduate student, master
student, PhD student provides its own implementation of GET
grant. So the logic for the grant
amount lives inside each student types class.
Precisely when you call student dot get grant polymorphism
ensures the correct version of the method for that specific
Student object gets executed. It elegantly distributes the
responsibility. That's cool.
It gets rid of the big conditional and makes it easy to
add new student types later without modifying the original
calling code. Exactly.
Much more extensible, adheres better to the open closed
principle. And finally a really important
one, especially in older code bases.
Remove dead code. Just.
Deleting code. Just deleting code that isn't
used anymore, methods that are never called classes, never
instantiated variables or attributes, never read stuff
that's just sitting there. That sounds simple, maybe even
obvious. But why is it so important,
especially in big old projects? It's not hurting anything if
it's not called, is it? But it is hurting things
indirectly in systems maintained over years by many different
developers. Dead code just accumulates.
It's inevitable, and it causes real problems.
It's clutter. It makes the code base bigger
and harder to navigate. New developers waste time trying
to understand code that does nothing.
They might be afraid to remove it, thinking it's used somewhere
they miss. Right, that fear factor.
What if this breaks something obscure?
Exactly. It adds confusion and slows down
comprehension. Removing it makes the code base
leaner, cleaner, easier to understand, and crucially,
easier and safer to change going forward.
You eliminate the risk of someone acts as evidently
resurrecting old buggy logic. It's essential maintenance.
OK, that makes total sense. It's not just tidiness, it's
risk reduction and maintainability.
So we've covered some really key refactoring techniques today.
Extract class for better structure.
Renaming using deprecation for safer transitions.
And those valuable local improvements like extract
variable, remove flag using polymorphism over conditional.
And definitely remove dead code. Keep that code base clean.
It really paints a picture of refactoring as this ongoing
process, not a one runoff event. Absolutely.
It's about continuous design improvement, making sure that as
the software grows and changes, the underlying structure stays
healthy, adaptable and maintainable.
It's an investment really in the future health of the project.
Thank you for joining us for this deep dive.