0% found this document useful (0 votes)
15 views50 pages

Week 6

Uploaded by

jikeb70500
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views50 pages

Week 6

Uploaded by

jikeb70500
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 50

CT-365 SOFTWARE ENGINEERING

LECTURE 6

IMPLEMENTATION
(REFACTORING & BAD
CODE PRACTICES)
HUMA TABASSUM
LECTURER
DEPT. OF COMPUTER SCIENCE & INFORMATION TECHNOLOGY
NED UNIVERSITY OF ENGINEERING & TECHNOLOGY
REFACTORING

2
Refactoring
Refactoring (noun): a change made to the internal structure of
software to make it easier to understand and cheaper to modify
without changing its observable behavior.

• Observable behavior – the term indicates that the code should,


overall, do just the same things it did before we started refactoring.
• Means nothing should change that the user should care about.
• Refactoring is very similar to performance optimization, as both
involve carrying out code manipulations that don’t change the overall
functionality of the program.
• The difference is the purpose.

• Refactoring is always done to make the code “easier to understand


and cheaper to modify.”
• This might speed things up or slow things down.

• With performance optimization, we only care about speeding up the


program, and are prepared to end up with code that is harder to work
with if we really need that improved performance.
4
WHY Should We Refactor?
• Refactoring Improves the Design of Software
• Without refactoring, the internal design—the architecture—of software tends to decay.
• As code is changed to achieve short-term goals, often without a full comprehension of
the architecture, the code loses its structure.
• It becomes harder to see the design by reading the code.
• Loss of the structure of code has a cumulative effect.
• The harder it is to see the design in the code, the harder it is to preserve it, and the more rapidly it
decays.
• Regular refactoring helps keep the code in shape.
• An important aspect of improving design is to eliminate duplicated code.
• Reducing the amount of code makes a big difference in modification of the code.
• The more code there is, the harder it is to modify correctly, and to understand it.
• By eliminating duplication, we ensure that the code says everything once and only
once, which is the essence of good design. 5
• Refactoring Makes Software Easier to Understand
• Refactoring helps make the code more readable.
• Before refactoring, we have code that works but is not ideally structured.
• A little time spent on refactoring can make the code better communicate its purpose;
means saying more clearly what we want.

• Why does it take some other programmer a week to make a change that
would have taken only an hour with proper understanding of the code?
• The trouble is that when we are trying to get the program to work, we are not thinking
about that future developer.

6
• Refactoring Helps to Find Bugs
• Help in understanding the code also means help in spotting bugs.
• If I refactor code, that means I work deeply on understanding what the code
does, and I put that new understanding right back into the code.
• By clarifying the structure of the program, I clarify certain assumptions I’ve
made—to a point where I can’t avoid spotting the bugs.
• Refactoring helps in being much more effective at writing robust code.

• Refactoring Helps to Program Faster


• When we talk about refactoring, we can easily see that it improves quality.
• Better internal design, readability, reducing bugs—all these improve quality.
• But doesn’t the time we spend on refactoring reduce the speed of development?

• In order to find out, let’s consider the graphs on the next slide.
7
• The difference between these
two is the internal quality of
the software.
• Software with a good internal
design allows us to easily find
how and where we need to
make changes to add a new
feature.
• If the code is clear, we’re less
likely to introduce a bug, and
if we do, the debugging effort
is much easier.
• By putting our effort into a
good internal design, we
increase the stamina of the
software effort, allowing us to
go faster for longer. 8
WHEN Should We Refactor?
• Preparatory Refactoring—Making It Easier to Add a Feature
• The best time to refactor is just before adding a new feature to the code base.
• Looking at the existing code, it is often seen that if it were structured a little
differently, the work would be much easier.
• For example, there is a function that does almost all that is needed, but has
some literal values that conflict with our needs.
• Without refactoring, we might copy the function and change those values.
• But that leads to duplicated code.
• So we will perform refactoring, and use Parameterized Function.
• Once it is done, we just have to call the function with the required parameters.

9
• Comprehension Refactoring: Making Code Easier to Understand
• We may have wanted to use some existing functions but spent several minutes
figuring out what they did because they were named badly.
• By refactoring we move the understanding from our head into the code itself.
• We then test that understanding by running the software to see if it still works.
• If we move our understanding into the code, it will be preserved longer and be
visible to our colleagues.
• Comprehension Refactoring on Little Details
• For example, renaming variables meaningfully, chopping a long function into
smaller parts etc.

• Ralph Johnson describes these early refactoring as wiping the dirt off a window
so you can see beyond.
• When we are studying code, refactoring leads us to higher levels of understanding that we
would otherwise miss.
• Those who dismiss comprehension refactoring as useless fiddling with the code, don’t
realize that by foregoing it they never see the opportunities hidden behind the confusion.
10
When NOT to Refactor?
• Refactoring is recommended — but there are cases when it’s not
worthwhile.
• If we run across code that is a mess, but we don’t need to modify it,
then we don’t need to refactor it.
• Some ugly code that can be treated as an API may remain ugly.
• It’s only when we need to understand how it works that refactoring gives us
any benefit.
• Another case is when it’s easier to rewrite it than to refactor it.
• This is a tricky decision.
• Often, we cannot tell how easy it is to refactor some code unless we spend
some time trying, and thus get a sense of how difficult it is.
• The decision to refactor or rewrite requires good judgment and experience. 11
12
Problems with Refactoring
• Refactoring is a valuable technique—one that should be used more by
most teams.
• But there are problems associated with it, and it is important to
understand how they manifest themselves and how to react to them.

• Slowing Down New Features


The whole purpose of refactoring is to make us program faster, producing more
value with less effort.
• There is a genuine tradeoff here.
• Sometimes, we do run into situations where we see a (large scale) refactoring
that really needs to be done, but the new feature we want to add is so small
that we prefer to add it and leave the larger refactoring alone. 13
• Code Ownership
• If we want to rename a function, and we can find all the callers to a function, we
can simply change the declaration and the callers.
• But sometimes this simple refactoring is not possible.
• Perhaps the calling code is owned by a different team and we do not have write
access to their repository.
• Code ownership boundaries get in the way of refactoring because we cannot
make the kind of changes we want.

• The solution is to allow team ownership of code—so that anyone in the same
team can modify the team’s code, even if originally written by someone else.
• Programmers may have individual responsibility for areas of a system, but that
should imply that they monitor changes to their area of responsibility, not block
them by default.
• Some teams encourage an open-source-like model where people from other
teams can change a branch of their code and send the commit in to be approved.
14
• Testing
• One of the key characteristics of refactoring is that it doesn’t change the
observable behavior of the program.
• If we follow the refactoring carefully, we shouldn’t break anything—but what if
we make a mistake?
• Mistakes happen, but they aren’t a problem provided we catch them quickly.
• Since each refactoring is a small change, if we break anything, we only have a
small change to look at to find the fault.
• This means that in most cases, if we want to refactor, we need to have self-
testing code.
• Self-testing code not only enables refactoring—it also makes it much safer to
add new features, since we can quickly find and kill any bugs we introduce.
• The key point here is that when a test fails, we can look at the change we’ve
made between when the tests were last running correctly and the current code.
• This also answers those who are concerned that refactoring carries too much
risk of introducing bugs.
15
• Legacy Code
Def: Any software/code which is still in use - but for which the original
developers are no longer around to fix stuff and answer questions about it.

• Refactoring can be a fantastic tool to help understand a legacy system.


• Functions with misleading names can be renamed so they make sense,
awkward programming constructs smoothed out, and the program turned
from a rough rock to a polished gem.
• But the biggest problem here is the common lack of tests.
• If you have a big legacy system with no tests, you can’t safely refactor it into
clarity.

16
An Example of Refactoring
• There are dozens of well-defined refactoring recipes for a huge range
of small, specific tasks. Let’s consider one.
• Inline Temporary Variable:
• Replace all uses of a local variable by inserting copies of its assigned value.
• We begin with a simple method:

17
• Lets identify all places in the code that need to change.
• In this case there's only one, in the return statement.
• We change it to use the value that's currently assigned to the temp:

• Finally, we can remove the now-obsolete declaration:

18
19
BAD CODE PRACTICES

20
• Programmers often
describe bad code as
having a “bad smell”
that needs to be
removed.
• “Code hygiene” is
another word for this.
• Let’s start with some
smelly code.

21
22
Don’t Repeat Yourself
• Duplicated code is a risk to safety.
• If you have identical or very similar code in two places, then the fundamental
risk is that there’s a bug in both copies, and some maintainer fixes the bug in
one place but not the other.
• Avoid duplication like you’d avoid crossing the street without looking.
• Copy-and-paste is an enormously tempting programming tool, and you should
feel danger every time you use it.
• The longer the block you’re copying, the riskier it is.
• Don’t Repeat Yourself, or DRY for short, has become a programmer’s
motto.
• The dayOfYear example in the previous slide is full of identical code.
• How would you DRY it out? 23
Avoid Magic Numbers
• There are really only two constants that computer scientists
recognize as valid: 0 and 1.

• All other constants are called magic because they appear as if out of
thin air with no explanation.

• One way to explain a number is with a comment, but a far better


way is to declare the number as a named constant with a good,
clear name.

24
• dayOfYear example is full of magic numbers:
• The months 2, …, 12 would be far more readable as FEBRUARY, …, DECEMBER.
• The days-of-months 30, 31, 28 would be more readable (and eliminate
duplicate code) if they were in a data structure like an array, list, or map, e.g.
MONTH_LENGTH[month].
• The mysterious numbers 59 and 90 are particularly harmful examples of
magic numbers.
• Not only are they uncommented and undocumented, they are actually the result of a
computation done by hand by the programmer.
• Don’t hardcode constants that you’ve computed by hand.
• Explicit computations like 31 + 28 make the origin of these mysterious numbers much
clearer.
• MONTH_LENGTH[JANUARY] + MONTH_LENGTH[FEBRUARY] would be clearer still.

25
Mysterious Name
• One of the most important parts of clear code is good names, so we
have to put a lot of thought into naming functions, modules,
variables, classes, so they clearly communicate what they do and how
to use them.
• Comments can often be avoided entirely by making the code itself
more readable, with better names that describe the methods and
variables. For example, you can rewrite:

26
• In general, variable names like tmp, temp, and data are awful,
symptoms of extreme programmer laziness.
• Every local variable is temporary, and every variable is data, so those
names are generally meaningless.
• Better to use a longer, more descriptive name, so that your code
reads clearly all by itself.
• Follow the lexical naming conventions of the language. In Java:
• methodsAreNamedWithCamelCaseLikeThis
• variablesAreAlsoCamelCase
• CONSTANTS_ARE_IN_ALL_CAPS_WITH_UNDERSCORES
• ClassesAreCapitalized
• packages.are.lowercase.and.separated.by.dots
27
Long Functions
• Writing code for humans is the golden
rule to being a good programmer.
• And long functions are one of the worst
enemies of this mindset since they
make our code less readable.
• Functions need to be descriptive about
what they do and should focus on a
single and isolated task so that we can
reuse them when necessary.
• Usually, as a function gets longer, its
complexity grows as well and the code
becomes less maintainable. 28
• As a rule of thumb, if you feel the need
to comment on something inside a
method, you should take this code and
put it in a new method.

• Even a single line can and should be


split off into a separate method, if it
requires explanations.

• And if the method has a descriptive


name, nobody will need to look at the
code to see what it does.

29
• In most of the cases, a solution to solve this problem is extracting a
portion of code into a new function (Extract Function) and reuse it
when necessary.
Problem Solution

30
Long Parameter List
• Long parameter list in a method call is a code smell.
• It indicates that there might be something wrong with the
implementation.
• A long parameter list is easy to spot.
• Symptoms of too many parameters include:
• It is hard to use a method call or to get the parameters in correct order.
• It is hard to read and interpret what a method call does.
• A method call has boolean parameters.
• A method call has null parameters as optional parameters.

• Consider the following example:


calculateStatistics(customer, unit, null, true, false); 31
• One of the many solutions to solve this problem is that, instead of
passing a group of data received from another object as parameters,
pass the object itself to the method, by using Preserve Whole Object.

32
Large Class
• A class that contains many fields/
methods/lines of code.
• There is a need to refactor it.
• Such a class has a huge number of lines of
code and many different methods.
• It is usually easier for a developer to add a
feature to an existing class rather than create
a new one, which is why the class grows.
• As a rule, too much functionality is crammed
into such a class.
• In this case, it helps to move part of the
functionality into a separate class. 33
Refactoring Technique for Large Class:
Extract Class
• If a class performs too
many functions, some of
them must be moved to
another class.
• For example, suppose we
have a Human class that
also stores a home address
and has a method that
returns the full address.
• This class needs to be
refactored.
34
• We have put the address
information and associated
method (data processing
behavior) into a separate class.

35
Rename Method
• Perhaps a method was poorly named from the very beginning—for
example, someone created the method in a rush and didn’t give
proper care to naming it well.
• Or perhaps the method was well named at first but as its functionality
grew, the method name stopped being a good descriptor.
• The recipe for Rename Method is presented in the book Refactoring:
Improving the Design of Existing Code, by Martin Fowler, Kent Beck,
John Brandt, William Opdyke, Don Roberts, and Erich Gamma (all
names you may associate with eXtreme Programming, patterns, and
software craft).

36
Steps
• See if the method is defined in a superclass or subclass.
• If so, you must repeat all steps in those classes too. (and run the tests)
• Create a new, empty method with a new name (and run the tests).
• Copy the code of the old method to the new method (and run the
tests).
• Delete all the code in the old method replacing it with a call to the
new method (and run the tests).
• Find all references to the old method and replace them with
references to the new one (and run the tests).
• Delete the old method.
• If the old method is part of a public interface, don’t perform this step.
• Instead, mark the old method as deprecated (and run the tests). 37
• The transitional steps are temporary, and temporarily introduce
smells to the code, but they are very short-lived “scaffolding” steps.
• You achieve the desired result quickly enough.
• It’s like cleaning a closet.
• You may have to make it worse (pull everything out into the room) before you
make it better (put it back in order).

Initial Implementation

38
Intermediate Renamed

39
Comments
• Comments are usually created with the best of intentions, when the
author realizes that his or her code is not intuitive or obvious.
• In such cases, comments are like a deodorant masking the smell of
fishy code that could be improved.
• The best comment is a good name for a method or class.
• If you feel that a code fragment cannot be understood without
comments, try to change the code structure in a way that makes
comments unnecessary because your code itself is now much more
understandable.

40
Refactoring Techniques
• If a comment explains a section of code, this section can be turned
into a separate method via Extract Method.
• The name of the new method can be taken from the comment text
itself for e.g. as we did in the long function example.
• We made a new method by the name printDetails() and we took this name
from the comment text.
• If a method has already been extracted, but comments are still
necessary to explain what the method does, give the method a self-
explanatory name.
• Use Rename Method for this.
41
Switch Statement Code Smell
• Imagine that we have some
client class that calculates the
area and perimeter of
particular geometrical shapes.

• The class Client has two


methods.
• calculateArea()
• calculatePerimeter()

42
• This entire class is
clearly a poor design
that limits the current
client to only work with
three types of shapes.

• The problem with


switch statements is
the duplication and
that is a code smell.

43
• To improve the design, we make Square, Rectangle and Circle to have
a common root.
• By that we mean that they implement a common interface, such as
Shape.
• Then we can eliminate the switch statement code smell very easily.
• We replace it with polymorphism.

Quick recall: Polymorphism describes the concept that different classes


can be used with the same interface. Each of these classes can provide
its own implementation of the interface.

• Now let’s refactor the previous code using technique, Replace


Conditional with Polymorphism. 44
45
• And all such
refactoring simplifies
the Client code.

• It also allows for easy


extensibility by
implementations of
other shapes without
changing a single line
of Client code.

46
47
Conclusion
• As a result of good refactoring, a program has easy-to-read code, the
prospect of altering its logic is not frightening, and introducing new
features doesn't become code analysis hell, but is instead a pleasant
experience.

• However, you shouldn't refactor if it would be easier to write a


program from scratch.
• For example, suppose your team estimates that the labor required to
understand, analyze, and refactor code will be greater than implementing the
same functionality from scratch.

48
49
50

You might also like