KEMBAR78
Refactoring Code | PDF | Source Code | Test Driven Development
0% found this document useful (0 votes)
31 views35 pages

Refactoring Code

Refactoring is the systematic process of improving existing code without altering its functionality, aimed at enhancing maintainability, readability, and reducing technical debt. It is an ongoing practice that should be integrated into the development workflow, focusing on small, incremental changes. Key indicators for refactoring include repetitive code, frequent bugs, and the need for cleaner interfaces when adding features or fixing issues.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
31 views35 pages

Refactoring Code

Refactoring is the systematic process of improving existing code without altering its functionality, aimed at enhancing maintainability, readability, and reducing technical debt. It is an ongoing practice that should be integrated into the development workflow, focusing on small, incremental changes. Key indicators for refactoring include repetitive code, frequent bugs, and the need for cleaner interfaces when adding features or fixing issues.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 35

Refactoring Code

Refactoring
Refactoring or Code Refactoring is defined as systematic process of improving existing
computer code, without adding new functionality or changing external behaviour of the
code. It is intended to change the implementation, definition, structure of code without
changing functionality of software. It improves extensibility, maintainability, and
readability of software without changing what it actually does. Why should we refactor
our code when it works fine? The goal of refactoring is not to add new functionality or
remove an existing one. The main goal of refactoring is to make code easier to maintain in
future and to fight technical debt. We do refactor because we understand that getting
design right in first time is hard and also you get the following benefits from refactoring:
• Code size is often reduced
• Confusing code is restructured into simpler code

“The code refactoring process involves restructuring existing code without


changing its external behavior. It’s like renovating a house to enhance
its
functionality and aesthetics while keeping the foundation intact. ”
• A higher level goal of restructuring is to increase the software value
• external software value: fewer faults in software is seen to be better by customers
• internal software value: a well-structured system is less expensive to maintain
• Simple examples of restructuring
• Pretty printing
• Meaningful names for variables
• One statement per line of source code
• Developers and managers need to be aware of restructuring for the following reasons
• better understandability
• keep pace with new structures
• better reliability
• longer lifetime
• automated analysis
• Characteristics of restructuring and refactoring
• The objective of restructuring and refactoring is to improve the internal and external values of
software.
• Restructuring preserves the external behavior of the original program.
• Restructuring can be performed without adding new requirements.
• Restructuring generally produces a program in the same language.
• Example: a C program is restructured into another C program.
Why Do You Need Code
Refactoring?
• Refactoring your old code is a good practice because it saves you from countless
headaches in the future. Just because some code works doesn't mean it's good
code. Sometimes the software will work, but it will either be limited in
performance because it’s written in an unoptimized way, or it will be harder to add
new features to it because it’s overly complex and without any rules. A lot of the
time it’s both. Taking the time to refactor your code is always a good idea. You will
end up with a much cleaner code that is easier to maintain and upgrade, and also
remove a lot of opportunities for bugs.
• Putting rules in place will help you ensure that the cleanliness and order of your
code will remain intact as your project grows as well. Use linters. Decide on coding
standards. Mind the formatting. Also, a well written code is a performant code.
Have you written or used a super slow app that still performs its tasks? Technically,
it does work, right? But it’s a pain to use it. That’s a very big reason to refactor it.
Why Do You Need Code
Refactoring?
• Refactoring is an ongoing process that should be performed regularly, ideally as
part of the development workflow. It is typically carried out iteratively, focusing
on small, incremental changes to minimize the risk of introducing errors or
regressions. Through refactoring, developers continuously improve the codebase,
ensuring its long-term sustainability and adaptability to changing requirements.
The refactored simple loop version removes the unnecessary
“else” block, avoids a potential stack overflow, and provides a
clearer understanding of the logic without losing the
functionality of calculating the factorial.

Now from here, a question can raise, then what is the difference
between code optimization and code refactoring?
Difference Between Code
Optimization and Code Refactoring?
• Code Optimization
• Improves resource efficiency, execution time, and performance of the code.
• Focuses on improving the memory or processing time.
• May change the original code to achieve performance gains.
• Involves changes to algorithmic or low-level implementation details.
• May sacrifice code readability to achieve performance targets.
• Code Refactoring
• Aims to improve the structure, readability, and maintainability of the code.
• Looks to enhance code organization without altering external behavior.
• Retains intrinsic functionality while enhancing the code quality.
• Involves holistic restructuring, simplifying, and clarifying code.
• Usually, doesn’t consider performance improvements as the main goal.
When Do You Need Code
Refactoring?
• There’s no alarm that goes off when it’s time to refactor your code,
but there are indicators that you should be mindful of that will tell
you it might be time for some code refactoring. Let’s see some of the
indicators and opportunities when you can do some refactoring:
• If you find yourself writing similar code multiple times, it might be a good time for a refactor.
• Multiple, or repetitive bugs in the same domain within the codebase. This means that most likely
the code within that domain is of low quality, so refactoring it would definitely be a good idea.
Using an application performance monitoring service like Sentry will help you in figuring out if there
are clusters of bugs around the same domain within the codebase.
• If you’re adding a new feature and you notice that the code you’re interfacing with is someone
else’s dirty code, refactoring it will of course clean it up so it’ll be easier to work with, but it’ll also
help you understand that part of the code better.
• Same goes when fixing bugs as well. When you’re fixing a bug, you’re actually refactoring old faulty
code. You could just patch it up and call it a day, or you could go the extra mile and do a bigger
cleanup around the parts where the bug occurred.
When Do You Need Code
Refactoring?
• Same goes when fixing bugs as well. When you’re fixing a bug, you’re actually refactoring old faulty
code. You could just patch it up and call it a day, or you could go the extra mile and do a bigger
cleanup around the parts where the bug occurred.
• There is a principle in software development named DRY, short for “Don’t Repeat Yourself”. It
suggests replacing any repetitive information that is likely to change with abstractions that are less
likely to change. The principle states that “every piece of knowledge (read
implementation/feature/logic) must have a single, unambiguous, authoritative representation
within a system”.
It doesn’t mean that you should abstract everything that might be repeatable in order to keep it as
a single representation. Be aware that abstraction is not a silver bullet, but a double-edged sword.
Too much abstraction will also make your code confusing and vague, thus making it harder to
maintain. This too can produce code smells, which is the exact opposite of what you wanted in the
first place.
As you can see, you need to find a good balance when abstracting code. There’s an article by Kent
C. Dodds that explains a good middle ground between duplicating and abstracting. He calls it “AHA
Programming”. AHA stands for “Avoid Hasty Abstractions”. The article summed up in one sentence
is “don’t be afraid to duplicate code until the similarities scream at you, then you’d abstract them”.
What are code smells and
technical debt?
 Code smells are symptoms of inferior code quality that can contribute to technical debt. They happen
when you step out of the coding standards and create structures that go against the design principles that
you’ve previously agreed upon. Code smells aren’t bugs. They won’t crash your app. They’re just
characteristics of poorly designed code that make it tightly coupled, hard to work with, or too rigid to be
reused. Does your code have a lot of duplication? Are your functions very long and is hard to figure out
what exactly they do? Those are some examples of code smells. They contribute to technical debt, and
can leave room for bugs in the future.
 Technical debt is the increased cost of maintenance caused by picking quick and easy but limited
solutions instead of better ones that take more time to implement. Just like a monetary debt, technical
debt accumulates “interest”, making your codebase harder to maintain. Realistically, it’s something that
occurs in almost every project, and sometimes purposefully introducing technical debt is a conscious
decision in order to push the project faster in order to pull off a deadline. Some examples include hasty or
incomplete implementations, lack of documentation, insufficient test coverage etc… You could imagine
how these things can keep piling up over time and cause all sorts of issues down the road.
What causes technical debt?
 Business pressure  Lack of tests
Sometimes business circumstances might force you to roll The lack of immediate feedback encourages quick, but
out features before they’re completely finished. In this risky workarounds or kludges. In worst cases, these
case, patches and kludges will appear in the code to hide changes are implemented and deployed right into the
the unfinished parts of the project. production without any prior testing. The consequences
 Lack of understanding of the consequences of technical can be catastrophic. For example, an innocent-looking
debt hotfix might send a weird test email to thousands of
customers or even worse, flush or corrupt an entire
Sometimes your employer might not understand that database.
technical debt has “interest” insofar as it slows down the  Lack of documentation
pace of development as debt accumulates. This can make it
This slows down the introduction of new people to the
too difficult to dedicate the team’s time to refactoring
because management doesn’t see the value of it. project and can grind development to a halt if key people
leave the project.
 Failing to combat the strict coherence of components  Lack of interaction between team members
This is when the project resembles a monolith rather than If the knowledge base isn’t distributed throughout the
the product of individual modules. In this case, any changes company, people will end up working with an outdated
to one part of the project will affect others. Team understanding of processes and information about the
development is made more difficult because it’s difficult to project. This situation can be exacerbated when junior
isolate the work of individual members. developers are incorrectly trained by their mentors.
How can you avoid technical
debt?
You could take the previous section “What causes technical debt?” and do the opposite. No really. I wrote
them in a way that will also give you an idea on how to avoid technical debt. But let’s make an actionable
list anyways:

• Agree upon coding standards from the very beginning, document the standards and enforce them while coding.
Make sure to point them out during code reviews.

• It’s very important to do code reviews. Very important! A code review is your last chance to catch potential bugs
and code smells before they get merged and contribute to the technical debt.

• Have tests in place. The tests will prevent quick and dirty fixes. It’s very easy to decline a PR whose tests are
failing.
How can you avoid technical
debt?
You could take the previous section “What causes technical debt?” and do the opposite. No really. I wrote
them in a way that will also give you an idea on how to avoid technical debt. But let’s make an actionable
list anyways:

• Don’t avoid or postpone code refactoring. Refactoring code is your “undo” action for pushing code smells and
dirty fixes to the main branch. Some code smells can slip through the code reviews as well. Also, as time goes by,
new versions of your dependencies are published which could contain breaking changes, requiring you to
refactor your code before upgrading them.

• It’s important to have a lead/senior engineer on the team that will enforce good practices for maintaining the
quality of the software. Maybe you can't just assign that role, but you can always discuss it with your team or
make a suggestion to your manager.

• Remember to take your time and thoroughly think through your solution as you develop it. Think about
important use cases that your part of the code will affect. Do a self-review before you open a PR. You can use
the Git Diff tool to highlight your changes, which makes self-reviewing your code easier.
Activities in a
Refactoring Process
To restructure a software system, one follows a process with well
defined activities.
 Identify what to refactor.
 Determine which refactoring to apply.
 Ensure that refactoring preserves the software’s behavior.
 Apply the refactoring to the chosen entities.
 Evaluate the impacts of the refactoring.
 Maintain consistency.
Identify what to refactor
 The programmer identifies what to refactor from a set of high-
level software artifacts.
 Next, focus on specific portions of the chosen artifact for
refactoring.
 The concept of code smell is applied to source code to detect
what should be refactored.
 Entities to be refactored at the design level
Determine which Refactoring to
apply
Some Refactoring are (Fig. 7.1)

• R1: Rename method print to process in class PrintServer.


• R2: Rename method print to process in class FileServer. (R1 and R2 are to be done together.)
• R3: Create a superclass Server from PrintServer and FileServer.
• R4: Pull up method accept from PrintServer and FileServer to the superclass Server.
• R5: Move method accept from PrintServer to class Packet, so that data packets themselves
will decide what actions to take.
• R6: Move method accept from FileServer to Packet.
• R7: Encapsulate field receiver in Packet so that another class cannot directly access this field.
• R8: Add parameter p of type Packet to method print in PrintServer to print the contents of a
packet.
• R9: Add parameter p of type Packet to method save in class FileServer so that the contents of
a packet can be printed.
Determine which Refactoring to
apply
Determine which Refactoring to
apply
A subset of the entire set of refactoring need to be carefully chosen because of the
following reasons.
 Some refactoring must be applied together.
Example: R1 and R2 are to be applied together.
 Some refactoring must be applied in certain orders.
Example: R1 and R2 must precede R3.
 Some refactoring can be individually applied, but they must follow an order if applied
together.
Example: R1 and R8 can be applied in isolation. However, if both of them are to be applied, then R1
must occur before R8.
 Some refactoring are mutually exclusive.
Example: R4 and R6 are mutually exclusive.
Ensure that refactoring preserves the
software’s behavior.
Ideally, the input/output behavior of a program after refactoring is the same as the behavior before refactoring.
In many applications, preservation of non-functional requirements is necessary.
A non-exclusive list of such non-functional requirements is as follows:
i. Temporal constraints: A temporal constraint over a sequence of operations is that the operations occur in a certain order.
For real-time systems, refactoring should preserve temporal constraints.
ii. Resource constraints: The software after refactoring does not demand more resources: memory, energy, communication
bandwidth, and so on.
iii. Safety constraints: It is important that the software does not lose its safety properties after refactoring.

Two pragmatic ways of showing that refactoring preserves the software’s behavior.
iv. Testing:
Exhaustively test the software before and after applying refactorings, and compare the observed behavior on a test-
by-test basis.

v. Verification of preservation of call sequence:


Ensure that the sequence(s) of method calls are preserved in the refactored program.
Fig. 7.2(a)

Apply the refactoring to chosen


entities
The class diagram of Fig. 7.2(a) has been obtained from Fig.
7.1 by
• focusing on the classes FileServer, PrintServer, and
Packet
• applying refactoring R1, R2, and R3.
Evaluate the impacts of the Refactoring on Quality
• Refactoring impact both internal and external qualities of software.
• Some examples of internal qualities of software are
• size, complexity, coupling, cohesion, and testability
• Some examples of external qualities of software are
• performance, reusability, maintainability, extensibility, robustness, and scalability
• In general, refactoring techniques are highly specialized, with one technique improving a small number of
quality attributes.

• For example,
• some refactoring eliminate code duplication;
• some raise reusability;
• some improve performance; and
• some improve maintainability
• Example of measuring external qualities
• Some examples of software metrics are coupling, cohesion, and size.
• Decreased coupling, increased cohesion, and decreased size are likely to make a software system more maintainable.
• To assess the impact of a refactoring technique for better maintainability, one can evaluate the metrics before
refactoring and after refactoring, and compare them
Code Refactoring Best
Techniques
• If you are wondering which is the best moment of the project to refactor the
code it is worth keeping in mind two key stages:
• before implementing new functionality into existing code
• after the product has been rolled out to the market when the software
development team can calmly focus on updates and patches
• Depending on the project and the capabilities of your team, you should choose a
refactoring technique that will allow you to achieve your goals. You can use one
of the most popular techniques:
1. The Red-Green Refactor
This is a common approach used in
Test-Driven Development (TDD).
It is a cycle that helps guide the software
development process by emphasizing test
coverage and code quality. The cycle consists
of three steps: red, green, and refactoring.
1. The Red-Green Refactor

• Red: In the “Red” phase, you write a failing


test case. This test case typically represents
a new feature or functionality that you want
to implement or a bug that needs fixing.
Initially, the test fails because the
corresponding code is not yet implemented
or contains a defect.
1. The Red-Green Refactor

• Green: In the “Green” phase, you write the


minimum amount of code required to make
the failing test pass. The focus is on making
the test case succeed without concerning
code quality or optimization. This step
ensures that the code meets the expected
behavior defined by the test.
1. The Red-Green Refactor

• Refactor: In the “Refactor” phase, you


improve the code’s structure, readability,
and performance without changing its
external behavior. This includes applying
code refactoring techniques, removing
duplication, improving naming conventions,
and optimizing the design. The goal is to
enhance the code’s maintainability and
quality while keeping the tests passing.
2. Refactoring by Abstraction
This technique is mostly used by developers when there is a need to do a large amount
of refactoring. Mainly we use this technique to reduce the redundancy (duplication) in
our code. This involves class inheritances, hierarchy, creating new classes and
interfaces, extraction, replacing
inheritance with the delegation,
and vice versa.
2. Refactoring by Abstraction
• Pull-Up/Push-Down method is the best example of this approach.
• Pull-Up method: It pulls code parts into a superclass and helps in the elimination of code
duplication.
• Push-Down method: It takes the code part from a superclass and moves it down into the subclasses.
• Pull up the constructor body, extract subclass, extract superclass, collapse hierarchy, form template
method, extract interface, replace inheritance with the delegation, replace delegation with
Inheritance, push down-field all these are the other examples.
• Basically, in this technique, we build the abstraction layer for those parts of the system that needs to
be refactored and the counterpart that is eventually going to replace it. Two common examples are
given below…
• Encapsulated field: We force the code to access the field with getter and setter methods.
• Generalize type: We create more general types to allow code sharing, replace type-checking code
with the state, replace conditional with polymorphism, etc.
3. Composing Method
• During the development phase of an application a lot of times we write long methods in our
program. These long methods make your code extremely hard to understand and hard to change.
The composing method is mostly used in these cases.
• In this approach, we use streamline methods to reduce duplication in our code. Some examples
are: extract method, extract a variable, inline Temp, replace Temp with Query, inline method, split
temporary variable, remove assignments to parameters, etc.
• Extraction: We break the code into smaller chunks to find and extract fragmentation. After that,
we create separate methods for these chunks, and then it is replaced with a call to this new
method. Extraction involves class, interface, and local variables.
• Inline: This approach removes the number of unnecessary methods in our program. We find all
calls to the methods, and then we replace all of them with the content of the method. After that,
we delete the method from our program.
4. Simplifying Methods
• There are two techniques involved in this approach…let’s discuss both of them.
• Simplifying Conditional Expressions Refactoring: Conditional statement in
programming becomes more logical and complicated over time. You need to simplify the logic in
your code to understand the whole program.
There are so many ways to refactor the code and simplify the logic. Some of them are:
consolidate conditional expression and duplicate conditional fragments, decompose conditional,
replace conditional with polymorphism, remove control flag, replace nested conditional with
guard clauses, etc.
• Simplifying Method Calls Refactoring: In this approach, we make method calls simpler and easier
to understand. We work on the interaction between classes, and we simplify the interfaces for
them.
Examples are: adding, removing, and introducing new parameters, replacing the parameter with
the explicit method and method call, parameterize method, making a separate query from
modifier, preserve the whole object, remove setting method, etc.
5. Moving Features Between
Objects
• In this technique, we create new classes, and we move the functionality safely
between old and new classes. We hide the implementation details from public
access.
• Now the question is… when to move the functionality between classes or how
to identify that it’s time to move the features between classes?
• When you find that a class has so many responsibilities and too much thing is
going on or when you find that a class is unnecessary and doing nothing in an
application, you can move the code from this class to another class and delete it
altogether.
• Examples are: move a field, extract class, move method, inline class, hide
delegate, introduce a foreign method, remove middle man, introduce local
extension, etc.
6. Preparatory Refactoring
• This approach is best to use when you notice the need for refactoring while adding some new features in an
application. So basically it’s a part of a software update with a separate refactoring process. You save yourself
with future technical debt if you notice that the code needs to be updated during the earlier phases of feature
development.

• The end-user can not see such efforts of the engineering team eye to eye but the developers working on the
application will find the value of refactoring the code when they are building the application. They can save
their time, money, and other resources if they just spend some time updating the code earlier.

“It’s like I want to go 100 miles east but


instead of just traipsing through the woods, I’m
going to drive 20 miles north to the highway
and then I’m going to go 100 miles east at
three times the speed I could have if I just
went straight there. When people are pushing
you to just go straight there, sometimes you
need to say, ‘Wait, I need to check the map
and find the quickest route.’ The preparatory
refactoring does that for me.”
Code Refactoring Tools
1. IntelliJ IDEA: A popular integrated development environment (IDE) that provides powerful
refactoring capabilities for various programming languages, including Java, Kotlin, and
JavaScript. It offers automated refactorings, such as extracting methods, renaming variables,
and introducing variables.
2. EclipseIDE: Another widely used IDE that supports refactoring for languages like Java, C/C++,
and Python. It offers automated refactorings like extract method, inline method, and rename.
3. Visual Studio Code: Microsoft’s integrated development environment that includes built-in
refactoring features for languages such as C#, Visual Basic, and C++. It provides refactorings like
extract method, renames, and extract interface.
4. ReSharper: A popular refactoring tool for Microsoft Visual Studio that supports C# and VB.NET.
It offers a wide range of automated refactorings, code inspections, and suggestions for
improving code quality.
5. SonarQube: A continuous code quality tool that can also identify code smells and
maintainability issues. It offers static code analysis and provides recommendations for
refactoring.
How to measure the
effectiveness of your refactor?
• There are multiple metrics you can use to measure the success of your code refactoring. You can
use Codecov to monitor your code coverage. Code coverage tells you what percentage of your
codebase is tested and can be trusted. Of course, a more extensive code coverage means a more
trustworthy codebase. Since code refactoring changes your codebase without changing any
functional behavior, having proper tests and having good coverage is crucial for ensuring that
your refactoring was indeed successful.

• Maybe your refactoring also fixes some bugs and/or improves the performance of your
application. In that case, you can use Sentry’s Release Health and Performance Monitoring
features to measure the percentage of crash-free sessions and your app’s performance to
determine if the code refactoring did in fact decrease the bugs and/or improved the
performance.
Main Benefits of Refactoring
• Improve code readability — Readable • Performance optimization — Refactoring can lead to
code is easier to understand and performance improvements by optimizing
maintain, leading to better algorithms, and data structures, or eliminating
collaboration among developers and unnecessary computations.
reduced time spent on deciphering • Increase code reusability — Refactoring promotes
source code. code reusability by extracting common functionality
• Enhance code maintainability — By into reusable methods or classes.
• Adapt to changing requirements — Refactoring
removing redundant code and
enables the codebase to be more flexible and
reducing complexity, refactoring
adaptable to evolving requirements. It helps in
enhances code maintainability and
removing unnecessary dependencies, reducing
simplifies modification.
coupling between components, and applying design
• Enhanced extensibility — Refactoring patterns to facilitate future modifications or feature
helps to isolate code components, additions.
making it easier to add or change • Facilitate code reviews and onboarding — Well-
functionality without affecting other refactored code is easier to review, understand, and
parts of the system. onboard new software developers.

You might also like