Code refactoring

Code refactoring

October 16, 2023

Major overhauling or redevelopment is something everybody faces at least once in their lives. Pipes and wiring wear out, finishing materials and interior items become outdated, the location of rooms ceases to be convenient. Refactoring is comparable to a major overhaul and involves redesigning and correcting the code. The internal structure of the program changes, while the behaviour remains the same.

Code refactoring is often required because:

  1. The architecture is subject to change;
  2. It is necessary to scale the solution;
  3. Productivity ought to be increased;
  4. The first version was made in a hurry or for “as cheap as possible”;
  5. There is a new version of the compiler or library;
  6. Past programmers may have made architectural mistakes;
  7. As the operation progresses, it becomes clear that new functions are needed.
Bicycle
Reinventing the bicycle
Info

Low-quality code is cumbersome and unreadable. It can easily be detected by common signs such as storing passwords in cookies, complex URLs, naming variables in different languages etc.

Refactoring fixes the following issues:

01 Heterogeneous style naming variables Heterogeneous style of naming variables, methods, classes
02 Code duplication Code duplication
03 Cluttered classes Cluttered methods and classes
04 An overloaded list An overloaded list of parameters of functions and methods
05 Long branching blocks Long branching blocks
06 Magic values Magic values
07 Incomprehensible abbreviations The use of incomprehensible abbreviations
08 Hard coding Hard coding of assumptions
09 Excessive configuration Excessive configuration
10 Confusing code Confusing code
11 Abstraction levels An excessive number of abstraction levels
12 Interdependence of classes Excessive interdependence of classes from one other
13 No data validation No input data validation
14 Overloading of classes Overloading of classes and components with heterogeneous functionalities
15 Generalization code Excessive generalization of the code
16 Temporary values in fields Storing temporary values in fields
17 Intermediary classes Intermediary classes
18 Lack of class isolation Lack of class isolation
19 Inappropriate class inheritance Inappropriate class inheritance
20 Inheritance instead of delegation Inheritance instead of delegation
21 Logic outside of the domain classes Logic outside of the domain classes
22 Direct calls Direct calls between distant parts of the system
23 Unused code Unused code
24 CPU consumption CPU consumption in standby
25 Incorrect parallelization CPU Incorrect parallelization of tasks or lack of multi-threading implementation
26 Separate branch A separate branch of the code for special cases
27 Library Self-written code in place of library-use
28 Insufficient commenting Insufficient commenting on the code or excessive commenting on obvious sections
Oak on a prop
Supporting a 600-year-old English oak at the Pskov-Pechersky Monastery

Refusal to refactor leads to the fact that any minor interference in the code will entail a series of errors and corrections. The second unpleasant consequence may be occasionally recurring critical errors that will be extremely difficult to reproduce, diagnose and debug. Finally, the gradual addition of new functions will lead to a layering of functionalities, which, in turn, will negatively affect the operation of the program and will damage the user experience.

Clean code

Clean code is a subjective concept among programmers regarding the proper way to write code. Typically, it is defined as code that is easy to read, comprehend, and is well-organized. Such code has minimal redundancy, is maintainable, easily testable, and its functionality can be effortlessly extended. Clean code possesses several distinguishing characteristics.

01 Tests Clean code passes all tests.
02 Clarity Clean code is obvious to other programmers.
03 Duplicates Clean code contains a minimal amount of duplication.
04 Maintenance Clean code is easy and cheap to maintain.

In software development, several principles and techniques exist to facilitate the creation of high-quality code, such as SOLID, YAGNI, KISS, and DRY, along with methodologies like Driven Development.

SOLID is an acronym representing five design principles for constructing object-oriented programs. Its objective is to promote the development of modular code, making it simpler to support and expand functionality, reduce coupling, and enhance adaptability to changes.

  • Single Responsibility Principle (SRP) emphasizes that each object should only have one responsibility, enhancing the code's readability and easing its maintenance.
  • Open/Closed Principle (OCP) maintains that applications should be open to extension but closed to modification, preserving existing functionality from unintended alterations.
  • Liskov Substitution Principle (LSP), proposed by Barbara Liskov, posits that any subclass should be interchangeable with its base class. For instance, “Elephant” and “Mouse” should be substitutable for the general class “Animal”.
  • Interface Segregation Principle (ISP) suggests that users shouldn't be forced to depend on interfaces they do not use. Every interface should only declare the methods required by its clients.
  • Dependency Inversion Principle (DIP) states that high-level modules should not rely on low-level ones; both should depend on abstractions instead.

Each SOLID principle addresses specific challenges; for instance, SRP mitigates the overlap of responsibilities among classes. These principles are intrinsically linked with design patterns such as “Strategy”, “Decorator”, “Factory”, “Adapter”, “Bridge”, and more, serving as crucial tools for effective software design and development.

Attention

The design patterns are discussed in a separate article.

The YAGNI and KISS principles (You Aren't Gonna Need It and Keep It Simple, Stupid, respectively) share similarities in their goal to produce code that is simple and easily understood. Applying these principles enables developers to concentrate on implementing essential features while avoiding undue complexity. YAGNI advises developers to refrain from adding code that isn't currently needed, allowing focus on required functionality without unnecessary deliberation on the entire architecture. Conversely, KISS advocates for simplicity, elevating it as a primary goal or value in system design. Both principles align with several design patterns, such as the “Facade”, “Composite”, and “Mediator”, contributing to more efficient and coherent software design and development.

The DRY principle, standing for “Don't Repeat Yourself”, advises developers against code duplication. Repeating code can lead to longer, more complex codebases, making it challenging for other developers to understand. It is preferable to reuse established and verified code whenever specific functionality is needed, reducing errors and development time. To address code repetition, design patterns like “Factory Method”, “Singleton”, and “Prototype” are implemented.

Driven Development methodologies, including Test Driven Development (TDD) and Domain Driven Design (DDD), are esteemed approaches in software development. Each approach has its merits and limitations, but they share a mutual objective of producing dependable software.

TDD emphasizes writing tests prior to developing code. This strategy ensures code functionality and helps developers swiftly spot and rectify errors before they escalate into substantial issues. The prompt detection and correction of errors are pivotal in the TDD approach.

DDD pays special attention to the business logic of the application. Developers build models that reflect the real world: domain objects and interactions between them. DDD also includes several key concepts, such as “Entity”, “Value Object”, “Aggregate”, “Repository”.

TDD and DDD represent more than just a collection of concepts and guidelines; they embody comprehensive approaches to software development. Conversely, the principles of SOLID, YAGNI, KISS, and DRY form a compilation of techniques aiding developers in optimizing code, minimizing coupling, simplifying modifications, and preventing unnecessary complexity and repetition. Integrating knowledge of these techniques with TDD and DDD methodologies enables developers to construct superior and dependable software solutions that effectively address business requirements.

Technical debt

Восклицательный знак в заштрихованном круге

“... I'm not interested in the quality of the code; the main thing is to write it cheaper and faster.”

Technical debt refers to a situation where developers opt for less-than-ideal solutions with the aim of meeting objectives swiftly, neglecting the potential long-term repercussions on the project. Essentially, it represents the fallout from compromises made during software or infrastructure development, resulting in future delays, malfunctions, failures, and resource overshoots. It illustrates the ramifications of prioritizing immediate progress over sustainable and scalable solutions, causing eventual impediments and increased workload in addressing the accumulated issues. Recognizing and addressing the root causes of technical debt are crucial for maintaining the health and efficiency of a development project.

01 Pressure from the client side Pressure from the client side.
02 Architectural problems Architectural problems.
03 Unsuccessful design-related decisions Unsuccessful design-related decisions.
04 Poor qualification of programmers Poor qualification of programmers.
05 Lack of sufficient testing. Lack of sufficient testing.
06 The postponing of refactoring. The postponing of refactoring.
07 Lack of documentation. Lack of documentation.
08 Lack of interaction between team members Lack of interaction between team members.
09 Lack of control over compliance with standards Lack of control over compliance with standards.
10 Improper use of third-party libraries Improper use of third-party libraries.

Teams that pay insufficient attention to the management of technical debt will face the growing complexity of their projects, dependence on old technologies and, ultimately, will consistently go over-budget. Therefore, debt control is an important element of the software development cycle.

01 Basic principles	Follow tips for writing clean code. Use SOLID, YAGNI, KISS and DRY to produce high-quality and efficient code.
02 Stages of development Pay attention to testing! Regular running of automated tests identifies and helps to eliminate errors at any stage of development.
03 Regular refactoring Regularly refactor the code, which will eliminate shortcomings, ensure code uniformity, simplify the support and “readability” of the code by other developers.
04 Debt solutions Make debt decisions consciously. When you have to get into debt to achieve short-term goals, remember — the debt will manifest itself in the future.

Industry leaders are investing significantly more resources in managing technical debt to create reliable software that will stand the test of time.

To conclude the discussion, we reiterate the importance of considering refactoring, clean code, and technical debt when developing and managing software applications. Maintaining clean code eases the enhancement of the application throughout its developmental phases.

Technical debt, on the other hand, often accumulates when the pace of development is prioritized over product quality. Finally, refactoring serves as a means to rectify accumulated flaws, promoting the longevity and efficiency of the software.