Abstract bad taste

Incomplete abstractions

This bad taste results when an abstraction does not support all complementary or related methods.

Why complete abstractions?

An important approach to abstraction implementation is to create cohesive and complete abstractions. When an abstraction does not support related methods, it can affect the cohesion and integrity of the abstraction. If the abstraction supports only partially related methods, its consumers may have to implement other functions themselves. A client may attempt to access abstract internal implementation details directly, with the side effect of violating the encapsulation principle.

Some common complementary approach pairs

Min/Max Open/Close Create/Destroy Get/Set
Read/Write Print/Scan First/Last Begin/End
Start/Stop Lock/Unlock Show/Hide Up/Down
Source/Target Insert/Delete First/Last Push/Pull
Enable/Disable Acquire/Release Left/Right On/Off

Reality to consider

### Prohibit specific behavior

The designer may make a conscious design decision not to provide stacking or matching methods. For example, in a read-only collection, include the Add() method but not the Remove() method.

Use individual methods instead of pairs

For example, replace Enable() and Disable() with SetEnabled(bool), passing true to Enable and fasle to Disable.

Multifaceted abstraction

This bad taste results when abstraction is given more than one responsibility.

## Why can’t there be multiple abstractions?

The single responsibility principle states that an abstraction must assume a single, clear responsibility and must fully encapsulate that responsibility. When abstract takes on a variety of responsibilities, it means that it will be affected by a variety of reasons and need to be modified. There is a strong positive correlation between the frequency of design modification and the number of defects. This means that multifaceted abstractions may have more flaws.

The underlying cause of multiple abstractions

General abstract

When an abstraction is introduced that uses a generic name (such as Item,Order,Product, Image), it is often used as a placeholder to provide all related (but not necessarily belonging) functionality. Abstract names represent the responsibilities of this abstraction. Naming is too generic, and as the system iterates, the abstraction slowly assumes multiple responsibilities. Empathy!!

Unscheduled refactoring

Extensive modifications to a class without regular refactoring may, over time, introduce additional responsibilities into the class.

Mixed concern

Not enough attention was paid to separation of concerns.

Reconstruction Suggestions

Classes are not cohesive when they have multiple responsibilities. You can use “extract classes” for refactoring.

Use extract classes to refactor multifaceted abstractions

Unused abstractions

This bad taste results when you create an abstraction that is not used (directly used or inherited). There are the following two forms of expression:

  • Unreferenced abstractions: Concrete classes that are not used
  • Widowed abstractions: There are no interfaces/abstract classes that derive abstractions

Why can’t we have abstractions that are not used?

Abstractions in design do not serve any purpose until they are used, thus violating the principle of abstraction. Unimplemented abstract classes and interfaces are redundant or imaginary generalizations and are therefore not needed.

## Potential causes of unused abstractions

Design out of thin air

Trying to design systems that are “timeless” or include abstractions that “may be useful in the future” can lead to this bad taste.

Changing needs

Requirements change, and abstractions created to meet earlier requirements may no longer be needed. If you leave it in the design, it becomes an abstraction that is not used.

Garbage left during maintenance

If you do not clean up old abstractions during maintenance or refactoring, you may be left with unreferenced abstractions. I know this very well, so I always ask my team members to delete the old code in the process of refactoring. If you don’t, expired and unused code leads to a code base that explodes, where refactored code gets entangled with unrefactored code, and the code is poorly understood and poorly read. And if you refactor old code and you’re not responsible for removing it, no one else will know how to do it, and over time it will become a breeding ground for bugs. Commenting out old code is also not a good option, as it would affect the reading experience. Second, today’s code versioning tools are so powerful that even if you delete the wrong code, you can use them to retrieve it. So the old code has to die.

Worry about breaking existing code

Not sure if there is any other code that is using the old code you want to remove.

Reconstruction Suggestions

Remove unused abstractions from the design. For apis that may still be in use by clients, it is not possible to simply delete them. You can mark these abstractions as “expired” or “deprecated,” explicitly stating that they cannot be used in newly developed clients.

[Obsolete]
public class Report
{
}
Copy the code

Reality to consider

Class libraries and frameworks typically provide extension points in the form of abstract classes or interfaces that may not be used in the library or framework, but are extension points for use by clients and therefore do not belong to unused abstractions.

Repeated abstraction

Two abstract names, implementations, or both can lead to bad taste.

  • The same name

    Two different abstract weights affect understandability.

  • Implement the same

    Multiple abstract member definitions are semantically identical, but the same elements in these implementations are not captured and used by design. In inheritance chromatography, if multiple sibling abstractions have the same implementation, it may mean that there is a bad taste of “unmerged hierarchies”.

  • The name implementation is the same

## Why can’t there be repeated abstractions?

Repetitive code is software’s worst sin. So we want to avoid repetition.

If multiple abstractions have the same name, it affects the understandability of the design: the guest code developer will not know which abstraction to use.

If implementations of multiple abstractions are the same (the code is the same), it can be difficult to maintain: changing the implementation of one of these abstractions often requires changing the implementation of all the other repeated abstractions. Not only does this increase the change burden, but it can also introduce minor bugs that are hard to find. To narrow the scope of modification, duplication must be avoided as much as possible.

The abstract underlying cause of repetition

Copy and paste programming techniques

CV programmers copy and paste code without applying appropriate abstractions.

Impromptu maintenance

After years of fixes or improvements, software can contain “residuals,” with lots of duplicate code.

miscommunication

Different people at different times are often responsible for maintaining software, who do not understand the software thoroughly and write pre-existing classes or methods, resulting in software containing duplicate code.

The ### class is declared unextensible

Classes are declared unextensible and cannot reuse code, only copy code and create revised versions.

Reconstruction Suggestions

For duplicate abstractions with the same name, you can change one of them to a different name.

For duplicate abstractions that implement the same, if the implementation is identical, one of the abstractions can be removed. If the implementation is slightly different, you can merge the same implementation into another class: this can be a base class in a hierarchy, or it can be a duplicate abstract existing or new class that can be referenced or used.

Reality to consider

Adapt to change

One reason for repeated abstractions is that both synchronous and asynchronous variants are supported.

Use the same type name in different contexts

For large systems, it is either not feasible or cost-effective to build a completely uniform domain model. One solution offered by domain-driven design is to divide large systems into multiple “bounded contexts”. In this way, models in different contexts may contain types with the same name, but this is acceptable.

The language does not provide support for repetition avoidance

In the JDK, there are a lot of duplicate methods and classes because there is no generic support for primitive types. But there are fewer duplicate methods and classes in.net because C# provides generic support for primitive types.

Reference: Software Design Refactoring

Akiko: That kind of thing

Disclaimer: This article is a summary of the blogger’s learning experience, the level is limited, if improper, welcome correction. If you think it’s good, you might as well pay attention to the wechat official account below
[The masturbation thing]To get the latest updates. Thank you for your support. Please indicate the source of reprint and quotation.
Wechat Official Account: