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