When de­vel­op­ing an ap­pli­ca­tion, in­el­e­gant­ly struc­tured sections can ac­cu­mu­late in the source code which impairs the usability and com­pat­i­bil­i­ty of the program. The solution is either an entirely new source code or re­struc­tur­ing in small steps. Many pro­gram­mers and companies in­creas­ing­ly opt for code refac­tor­ing in order to optimize func­tion­ing software over the long term and make it more legible and clearer for other pro­gram­mers.

During the refac­tor­ing process, the question is raised about which problem in the code should be solved with which method. Refac­tor­ing is meanwhile con­sid­ered to be among the basics when learning to code and is becoming more and more important. Which methods are used to this end and what are the ad­van­tages and dis­ad­van­tages?

What is refac­tor­ing?

Pro­gram­ming software is a lengthy process that can involve multiple de­vel­op­ers. Written source code is often revised, changed, and expanded during this work. As a result of time pressure or outdated practices, inelegant sections can ac­cu­mu­late in the source code. These are known as code smells. These weak spots that accrue over time endanger the usability and com­pat­i­bil­i­ty of the program. To prevent this gradual erosion and de­te­ri­o­ra­tion of the software, refac­tor­ing is necessary.

In principle, refac­tor­ing is similar to editing a book. The practice of editing does not create a com­plete­ly new book, but instead a more un­der­stand­able text. Just like various ap­proach­es exist in editing such as cutting, re­for­mu­lat­ing, deleting, and re­struc­tur­ing, code refac­tor­ing likewise en­com­pass­es a number of methods like en­cap­su­la­tion, re­for­mat­ting, or ex­trac­tion in order to optimize a code without changing its function.

This process is much more cost-effective than preparing an entirely new code structure. Es­pe­cial­ly in iterative and in­cre­men­tal software de­vel­op­ment, as well as agile software de­vel­op­ment, refac­tor­ing plays a major role, since pro­gram­mers fre­quent­ly need to alter software in these cyclical models. In this context, refac­tor­ing is a fixed step in the workflow.

When source code de­te­ri­o­rates: spaghetti code

First, it’s important to un­der­stand how code can age and mutate into spaghetti code. Whether due to time pressure, lack of ex­pe­ri­ence, or unclear in­struc­tions, pro­gram­ming code can lead to a loss of func­tion­al­i­ty as a result of un­nec­es­sar­i­ly com­pli­cat­ed commands. A code de­te­ri­o­rates in­creas­ing­ly, the faster and more complex an area of ap­pli­ca­tion is.

Spaghetti code refers to confusing, un­read­able source code that can only be in­ter­pret­ed by pro­gram­mers with great dif­fi­cul­ty. Simple examples of confusing code include su­per­flu­ous jump commands (GOTO) that instruct the program to skip back and forth in the source code, or un­nec­es­sary for/while loops and if commands.

Projects involving many software de­vel­op­ers are par­tic­u­lar­ly sus­cep­ti­ble to unclear source code. When code passes through many hands and if the original already contains some weak points, a growing mess resulting from “workaround solutions” can hardly be avoided, ne­ces­si­tat­ing a costly code review. In severe cases, spaghetti code can jeop­ar­dize the entire de­vel­op­ment of software. If the problem gets that far, it may even be too late for code refac­tor­ing.

Code smells and code rot are not quite so dis­as­trous. Over time, a code can start to smell – metaphor­i­cal­ly – with all its inelegant sections. Difficult-to-un­der­stand parts become worse as other pro­gram­mers intervene or add new strings. If refac­tor­ing is not performed at the first signs of code smell, the source code will gradually lose func­tion­al­i­ty as a result of code rot.

The aim of refac­tor­ing

The intention behind refac­tor­ing is simply to achieve better code. Effective code allows new code elements to be in­te­grat­ed better without in­tro­duc­ing new errors. Pro­gram­mers who can ef­fort­less­ly read the code will be able to fa­mil­iar­ize them­selves with a de­vel­op­ing ap­pli­ca­tion faster and remove or avoid bugs more easily. Another goal of refac­tor­ing is to improve error analysis and the main­tain­abil­i­ty of software. The work of pro­gram­mers reviewing code is therefore sim­pli­fied con­sid­er­ably.

What sources of errors does refac­tor­ing solve?

The tech­niques applied in refac­tor­ing are as varied as the errors they’re intended to remove. Es­sen­tial­ly, code refac­tor­ing is defined by its errors and en­com­pass­es the steps required to shorten or remove a solution approach. Sources of errors that can be resolved with refac­tor­ing methods include:

  • Confusing or excessive code: Command strings and blocks are so long that external pro­gram­mers will be unable to un­der­stand the internal logic of the software.
  • Code du­pli­ca­tions (re­dun­dan­cies): Unclear code often contains re­dun­dan­cies that have to be changed sep­a­rate­ly at each oc­cur­rence during main­te­nance, thereby wasting time and resources.
  • Excessive parameter lists: Objects are not assigned directly to a method but their at­trib­ut­es are conveyed in a parameter list.
  • Classes with too many functions: Classes with too many functions defined as methods – also known as god objects –make adjusting the software almost im­pos­si­ble.
  • Classes with too few functions: Classes with so few functions defined as methods that they are un­nec­es­sary.
  • Overly general code with special cases: Functions with too specific special cases that hardly ever occur – if at all – and therefore make adding necessary ex­ten­sions more difficult.
  • Middle man: A separate class acts as a “middle man” between methods and various classes, instead of directing calls from methods directly to a class.

What approach does refac­tor­ing involve?

Refac­tor­ing should always be performed before changing a program function. It ideally involves very small steps, with code changes tested using software de­vel­op­ment processes like test-driven de­vel­op­ment (TDD) and con­tin­u­ous in­te­gra­tion (CI). In a nutshell, TDD and CI refer to the con­tin­u­ous testing of small, new code sections that pro­gram­mers build, integrate, and test in terms of their func­tion­al­i­ty – often with automated test runs.

As a rule, only change the program in small steps in­ter­nal­ly, without affecting the external function. After each change, you should run an automated test run if possible.

What tech­niques exist

A range of refac­tor­ing tech­niques exist. A complete overview can be found in the com­pre­hen­sive book on refac­tor­ing by Martin Fowler and Kent Beck: Refac­tor­ing: Improving the Design of Existing Code. Here’s a brief summary:

Red-green de­vel­op­ment

Red-green de­vel­op­ment is a test-driven method of agile software de­vel­op­ment. It is used when a new function is to be in­te­grat­ed into existing code. Red stands for the first test run prior to im­ple­ment­ing a new function in the code. Green stands for the simplest possible code section required for the function in order to pass the test. As a result, an extension is prepared with constant test runs to filter out defective code and increase func­tion­al­i­ty. Red-green de­vel­op­ment provides a foun­da­tion for con­tin­u­ous refac­tor­ing in con­tin­u­ous software de­vel­op­ment.

Branching by ab­strac­tion

This refac­tor­ing method describes a gradual change to a system and the con­ver­sion of old, im­ple­ment­ed code into new, in­te­grat­ed sections. Branching by ab­strac­tion is typically used for large ap­pli­ca­tions that involve class hi­er­ar­chies, in­her­i­tance, and ex­trac­tion. By im­ple­ment­ing an ab­strac­tion that remains linked to an old im­ple­men­ta­tion, other methods and classes can be linked with the ab­strac­tion and the func­tion­al­i­ty of the old code section can be replaced by ab­strac­tion.

This often occurs via pull-up or push-down methods. They link to a new, better function with the ab­strac­tion and transfer the links to it. In doing so, they either move a sub-class to a higher class (pull-up) or divide a higher class into sub-classes (push-down).

You can then delete the old functions without en­dan­ger­ing the overall func­tion­al­i­ty. With these small changes, the system works unchanged while you gradually replace inelegant code with neat code, section by section.

Compiling methods

Refac­tor­ing is intended to make code methods as legible as possible. Ideally, external pro­gram­mers should be able to grasp the internal logic of a method when reading the code. There are a number of different tech­niques for ef­fi­cient­ly compiling methods. The aim of each change is to harmonize methods, remove re­dun­dan­cies, and split ex­ces­sive­ly long methods into separate sections, thereby opening them up to future changes.

Such tech­niques include:

  • Method ex­trac­tion
  • Method inlining
  • Removing temporary variables
  • Replacing temporary variables with a request method
  • In­tro­duc­ing de­scrip­tive variables
  • Sep­a­rat­ing temporary variables
  • Removing as­sign­ments to parameter variables
  • Replacing a method with a method object
  • Replacing an algorithm

Moving at­trib­ut­es between classes

To improve code, you need to move at­trib­ut­es or methods between classes. Here, the following tech­niques are used:

  • Move method
  • Move attribute
  • Extract class
  • Inline class
  • Hide delegate
  • Remove class in the middle
  • Introduce extrinsic method
  • Introduce local extension

Data or­ga­ni­za­tion

This method aims to divide data into classes and keep them as neat and clear as possible. You should remove un­nec­es­sary links between classes, which impair the software func­tion­al­i­ty in the event of minor changes, and divide them into coherent classes.

Examples of tech­niques include:

  • En­cap­su­lat­ing own attribute accesses
  • Replacing own at­trib­ut­es with an object reference
  • Replacing a value with a reference
  • Replacing a reference with a value
  • Linking ob­serv­able data
  • En­cap­su­lat­ing at­trib­ut­es
  • Replacing a dataset with a data class

Sim­pli­fy­ing con­di­tion­al ex­pres­sions

While refac­tor­ing, you should simplify con­di­tion­al ex­pres­sions as far as possible. The following tech­niques can be applied to this end:

  • Stripping con­di­tions
  • Merging con­di­tion­al ex­pres­sions
  • Merging repeated in­struc­tions in con­di­tion­al ex­pres­sions
  • Removing control switches
  • Replacing nestled con­di­tions with guard clauses
  • Replacing case dis­tinc­tions with poly­mor­phism
  • In­tro­duc­ing zero-objects

Sim­pli­fy­ing method requests

Method requests can be run faster and more easily using the following methods, for example:

  • Renaming methods
  • Adding pa­ra­me­ters
  • Removing pa­ra­me­ters
  • Replacing pa­ra­me­ters with explicit methods
  • Replacing error codes with ex­cep­tions

Refac­tor­ing example: renaming methods

The following example shows that the method naming in the original code does not make its func­tion­al­i­ty clear and easy to un­der­stand. The method is intended to output a ZIP code for an office address, but it doesn’t indicate this task directly in the code. To formulate the code more clearly, it’s a good idea to rename the method in the process of code refac­tor­ing.

Before:

String getPostalCode() {
	return (theOfficePostalCode+“/“+theOfficeNumber);
}
System.out.print(getPostalCode());

After:

String getOfficePostalCode() {
	return (theOfficePostalCode+“/“+theOfficeNumber);
}
System.out.print(getOfficePostalCode());

Refac­tor­ing: ad­van­tages and dis­ad­van­tages

Ad­van­tages Dis­ad­van­tages
Better com­pre­hen­si­bil­i­ty fa­cil­i­tates main­te­nance and the ex­tendibil­i­ty of the software Imprecise refac­tor­ing could introduce new bugs and errors into the code
Re­struc­tur­ing the source code is possible without altering the func­tion­al­i­ty There is no clear de­f­i­n­i­tion of “neat code”
Improved leg­i­bil­i­ty improves the com­pre­hen­si­bil­i­ty of the code for other pro­gram­mers An improved code is often difficult for the customer to recognize, since the func­tion­al­i­ty stays the same, i.e. the benefit is not self-evident
Removed re­dun­dan­cies and du­pli­ca­tions improve the ef­fec­tive­ness of the code In the case of larger teams working on refac­tor­ing, the co­or­di­na­tion effort required could be sur­pris­ing­ly high
Self-contained methods prevent local changes from having an effect on other parts of the code  
Clean code with shorter, self-contained methods and classes is char­ac­ter­ized by better testa­bil­i­ty  

In general, when refac­tor­ing, introduce new functions only when the existing source code is to remain unchanged. Only alter the source code – i.e. carry out refac­tor­ing – when you are not adding any new functions.

Go to Main Menu