Fundamentals 19 min read

Code Refactoring: Concepts, Practices, and Techniques

The article shares practical lessons from refactoring a ride‑hailing order system, explaining Martin Fowler’s definition of refactoring, distinguishing small (renaming, deduplication) and large (architectural redesign) efforts, outlining typical code smells, and detailing a structured pre‑, during‑, and post‑refactoring process to improve maintainability, reduce bugs, and accelerate development.

Amap Tech
Amap Tech
Amap Tech
Code Refactoring: Concepts, Practices, and Techniques

Recently the author participated in the refactoring of the Gaode ride‑hailing order Push project and shares the experience of code refactoring to inspire developers.

The only effective metric for code quality is: WTFs (what the fuck) per minute.

When working on a feature, developers often spend a lot of time locating related code, or feel "scared" when reading someone else’s messy code with unclear structure, vague variable and method names. This is not the developer’s fault; the code itself needs refactoring.

What is Refactoring?

As a noun (Martin Fowler): Refactoring is an adjustment of the internal structure of software, aimed at improving understandability and reducing modification cost without changing observable behavior. As a verb (Martin Fowler): Refactoring uses a series of refactoring techniques to adjust the structure without changing observable behavior.

The article focuses on the verb definition of refactoring and distinguishes between small refactoring and large refactoring .

Small Refactoring

Small refactoring deals with details at the class, function, or variable level, such as renaming poorly named variables, eliminating huge functions, and removing duplicate code. It is usually localized, simple, and can be done during daily development.

Large Refactoring

Large refactoring targets the top‑level architecture, including system, module, code, and class relationships. It often involves service layering, module modularization, componentization, and abstraction reuse. Large refactoring requires redefining principles, patterns, or even business logic and carries higher risk and longer duration.

Why Refactor?

Maintain a good software architecture that can provide stable services and handle incidents gracefully.

Increase maintainability and reduce maintenance cost, creating a positive feedback loop for teams and individuals.

Accelerate development speed and reduce labor cost; a well‑designed codebase prevents the system from “rotting” and speeds up feature delivery.

Common concerns that discourage developers from refactoring include lack of experience, unclear short‑term benefits, fear of introducing bugs, and extra effort for code they did not write.

Typical Code Smells

Generic Erasure

//{"param1":"v1", "param2":"v2", "param3":30, ……}</code><code>Map map = JSON.parseObject(msg); //【1】</code><code>……</code><code>// Pass map to lower‑level interface</code><code>xxxService.handle(map); //【2】</code><code><br/></code><code>// Lower‑level interface definition</code><code>void handle(Map<String, String> map); //【3】

Passing a raw Map to a generically typed interface can cause ClassCastException when non‑String values appear.

Useless Comments

Config config = new Config();</code><code>// set name and md5</code><code>config.setName(item.getName());</code><code>config.setMd5(item.getMd5());</code><code>// set value</code><code>config.setTypeMap(map);</code><code>// log</code><code>LOGGER.info("update done ({},{}), start replace", getName(), getMd5());

Comments that do not add information become noise.

Excessive if‑else

try {</code><code>  if (StringUtils.isEmpty(id)) {</code><code>    if (StringUtils.isNotEmpty(cacheValue)) {</code><code>      if (StringUtils.isNotEmpty(idPair)) {</code><code>        if (cacheValue.equals(idPair)) {</code><code>          // xxx</code><code>        } else {</code><code>          // xxx</code><code>        }</code><code>      }</code><code>    } else {</code><code>      if (StringUtils.isNotEmpty(idPair)) {</code><code>        // xxx</code><code>      }</code><code>    }</code><code>    if (xxxx(xxxx){</code><code>      // xxx</code><code>    } else {</code><code>      if (StringUtils.isNotEmpty(idPair)){</code><code>        // xxx</code><code>      }</code><code>      // xxx</code><code>    }</code><code>  } else if (!check(id, param)) {</code><code>    // xxx</code><code>  }</code><code>} catch (Exception e) {</code><code>  log.error("error:", e);</code><code>}

Deeply nested conditionals severely hurt readability and increase the risk of bugs.

Other Smells

Duplicate code – identical code blocks in multiple places should be extracted into a common method or class.

Long functions – violate the Single Responsibility Principle.

Poor naming – names should be self‑explanatory.

Useless code – dead code or commented‑out blocks should be removed.

God classes – classes that do too many things should be split according to responsibilities.

Large Refactoring Process

Pre‑Refactoring

Clarify the content, purpose, direction, and goals of the refactoring.

Gather data: map existing business, architecture, service dependencies, and produce design diagrams.

Project initiation: hold meetings, announce timeline, assign owners, and communicate impact.

During Refactoring

Architecture design and review – iterate until the team reaches consensus.

Detailed implementation plan – cover coding, unit testing, integration testing, AB testing, and rollout strategy.

AB validation – run the same input through old and new flows, compare results, and control rollout via gray‑scale and execution switches.

Code development, testing, and offline deployment – ensure test coverage and AB validation before production rollout.

Post‑Refactoring

Online rollout – gradual traffic increase (1%, 5%, 10%, …) while monitoring errors and metrics.

Business execution switch – after validation, enable write operations in the new flow.

Final cleanup – decommission old logic and AB validation code, conduct a retrospective to capture lessons learned.

Images illustrating the process are omitted for brevity.

Conclusion

Key coding tips include adhering to single‑responsibility, depending on abstractions rather than concrete implementations, following coding standards, using TODO/FIXME/XXX tags, and maintaining comprehensive unit, integration, and functional tests.

Remember: we are the authors of code, and future developers are our readers. Write code that future developers can understand and maintain, and avoid the "broken‑window" effect by continuously improving the codebase.

software architectureSoftware Engineeringcode refactoringbest practicestechnical debt
Amap Tech
Written by

Amap Tech

Official Amap technology account showcasing all of Amap's technical innovations.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.