Refactor Like a Superhero

How to refactor code without pain and do it efficiently.

Hello world! 👋

I'm Alex, a web developer at 0+X.

I've been writing code more than 10 years now and by this time I've seen some good code and a really bad one. In this talk, I want to share with you some technics I use to painlessly refactor bad code.

Disclaimer

Refactoring

Refactoring is the process of restructuring existing computer code without changing its external behavior. [It's] intended to improve the design, structure, or implementation...

Profits for Developers

Refactoring takes effort, but in return we achieve:

Profits for Business

“Bad” code

Characteristics are subjective and difficult to measure directly. But they all refer to “how readable the code is”.

...If you try hard, you can measure the characteristics, of course, but about that later 🤫

Heuristics for Identifying “Bad” Code

Cognitive helpers that tell us how to recognize “bad” code.

Code Smells

Sometimes it's enough to look at the list of code smells, find the right one, and use a recipe against it.

Developer Buzzwords™

💩 → 🍬

From Simple to Complex

Before Start

Rule of Thumb

Examples on GitHub!

Take a look at the commit history. There I showed how to apply different techniques and use buzzwords™ to find problems with the code.

Example of a commit message that explains what was done to the code and why

Formatting

Applying Prettier to a piece of code

Linters

At the very least, they can help find “dead” code and unused variables.

Linter errors at an unused variable

Language Features

Sometimes, you can replace the ternary operator with  Math.min
Serialization with FormData works as before but is more compact and extensible

Browser Features

You can get rid of extra code if you use language and environment features.

Environment Features

Use automated refactoring tools
...But check the changes they apply

Too Short Names and Abbreviations

Avoid abbreviations, they reflect too little context

Too Long Names

Don't keep unnecessary details in the name

Naming Option

The A/HC/LC Pattern suggests using a maximum of two contexts per entity

Follow the Context

If something is clear from the context, it can be omitted. But be careful with it.

If something is clear from the context, it can be omitted

Names for Different Entities

Avoid same names for different entities

Ubiquitous Language

Use domain entities for modelling
Model the domain in types

Functions and Deduplication

Extract repeating actions in functions

Functions and Abstraction

Abstract implementation details

Complexity Doesn't Disappear...

...But it becomes easier to deal with.

Complexity is easier to deal with when implementation details are abstracted behind a clear name

Like in Real Life

Abstraction helps to work with complex concepts

Implementation Details

Abstract implementation details

Separation of Concerns

Entity must be responsible for only 1 thing

Functional Pipeline

Focus on data transformations. Think of their states as the results of business events.

Focus on states and data transformations as results of business events

Separating States

If you differentiate the stages of the order lifecycle, it becomes clear that there are actually two functions

Adding States

It is easier to add new transformations to a functional pipeline

Conditions

Flat Conditions

Extract repeating conditions into variables.

Simplify patterns with Boolean logic rules.

!(a && b) === !a || !b
!(a || b) === !a && !b

Put recurring conditions into variables, this will help you see a pattern

Early return

Early return helps “unravel” difficult conditions

Predicates

Predicates abstract the details of verification, leaving the code declarative

Immutable by default

Immutability gives confidence in what the variable contains, eliminates sudden changes in it, and reduces the scope of its search

Referential Transparency

Pure functions and immutability are a perfect example of abstraction. If we can replace the function with its result we can “abstract” its details with the result.

Predicates abstract the details of verification, leaving the code declarative

Side Effects and Error Handling

Impureim Sandwich

Impureim, because:

  • impure side effect.
  • pure data transformations.
  • impure side effect.
Side effects “wrap” business rules made from pure functions

Impureim Grouping

Impureim helps to arrange the business rule code in the center and the side effects at the edges.

Impureim grouping helps to collect all the important code in one place

Command-Query Separation

Avoid mixing side effects and returning values in the sam function, apply command-query separation principle

Make Invalid States Unrepresentable

Type systems can highlight problems with encapsulation, invalid data states and CQS violation.

When using statically typed language, make invalid object states unrepresentable so you can't accidentally violate encapsulation

Errors

Result-containers

A container is a box inside which is the result of some unsafe function

Abstract Service Code

In JavaScript you can use higher-order functions to abstract service code

Poor Architecture Issues

When the modules are heavily coupled, it is difficult to expand the program

Layers and Dependency Direction

Business rules must not depend on external services
In the code, we implement this behavior using adapters

Adapters and Testability

Decoupled code is easier to test because it's easier to replace dependencies in such code

Split UI and Logic

A component with everything in it is hard to understand, test and modify.

Distinguish UI-logic from the domain logic

Declarativity

Explain what the code should do, not how.

Abstraction makes the code declarative

Imperative Error-Prone Code

In declarative style it is harder to make a mistake

Sometimes You Need Imperative Tho 🙄

// 🐌
array.map(x => x > 0.5);

// 🐇
for (const x of array) {
  x > 0.5;
}
          

“How Much Time Do I Need to...”

Main Takes

Technics and Recipes

Alex Bespoyasov 👋