Abstract: There is a saying that “the quality of code determines the quality of life”, when you reduce the complexity of software, reduce bugs, system maintainability is higher, naturally also leads to a better quality of life.

This article was shared from Huawei Cloud Community “Writing code is too complicated? See what the experts say”, the original author: Yuan Runzi.

preface

In the process of software development, we always pursue the high maintainability of software. High maintainability means that when there are new requirements, the system is easy to expand. It is easy for developers to locate bugs when they occur. When we say that the maintainability of a system is too poor, we usually mean that the system is so complex that it is prone to bugs when new features are added to the system, and then it is difficult to locate bugs when they appear.

So how is complexity defined in software?

John Ousterhout gives the following definition:

Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.

It can be seen that the complexity of software is a very general concept. Anything that makes the software difficult to understand and modify belongs to the complexity of software. To this end, John Ousterhout proposed a formula to measure the complexity of a system:

In the formula, pp represents the module in the system, C_ {p}cp represents the Cognitive Load of the module (i.e. the degree to which a module is difficult to understand), and T_ {p}tp represents the development time spent on the module in daily development.

From the formula, the complexity of a software is accumulated by the complexity of its modules, and module complexity = module cognitive burden * module development time, that is, the complexity of a module is related to both the module itself and the development time spent on the module. It is important to note that if a module is very difficult to understand, but it is barely touched upon in subsequent development, then its complexity is also low.

Causes of software complexity

There are many different causes of software complexity, but they can be summed up in two categories: dependencies and obscurity. The former is laborious and bug-prone, for example, when modifying module 1, it often involves module 2, module 3… Changes; The latter can make the software hard to understand, and it can take a lot of time to locate a bug or even just read a piece of code.

Software complexity is often accompanied by several symptoms:

Change amplification. When only one function needs to be changed, but many modules have to be changed, we call it shotgun modification. This is usually caused by too much coupling between modules and too much dependence on each other. For example, if you have a set of Web pages, each page is an HTML file, and each HTML has a background property. Because the individual HTML background attributes are defined separately, if you need to change the background color from orange to blue, you need to change all of the HTML files.

Cognitive load. When we say that a module is obscure and difficult to understand, it puts an excessive cognitive burden on it, and it often takes a lot of time for the reader to understand what the module does. For example, provide an unannotated calculate interface that has two int inputs and a return value of type int. From the signature of the function, the caller has no way of knowing what the function does. He can only call the function after spending time reading the source code to determine what the function does.

int calculate(int val1, int val2);

Unknown unknowns. Uncertainty is more damaging than the first two symptoms, and it usually refers to something that you have to be aware of, but don’t know about, when developing requirements. It is often caused by some hidden dependencies, which can make you feel at a loss after developing a requirement, vaguely feeling that there is something wrong with your code, but not sure where the problem is, and can only hope that the bug will be exposed in the testing phase rather than the commercial phase.

How do you reduce software complexity

Say No to tactical programming.

Tactical Programming (Tactical Programming) is one of the most commonly used methods of feature development and bug fixing that most programmers focus on how to get the program running quickly and easily. This is the classic Tactical Programming method, which seeks short-term benefits — saving development time. The most common manifestation of tactical programming is the lack of module design before coding, and the ability to write wherever you want. Tactical programming may be convenient in the early stages of a system, but once the system becomes large and the coupling between modules becomes heavy, it becomes difficult to add or modify features and fix bugs. As systems become more and more complex, they eventually have to be refactored or even rewritten.

The opposite of tactical programming is Strategic programming, which seeks long-term benefits — increasing system maintainability. Just making the program run isn’t enough. You also need to think about the maintainability of the program so that it can respond quickly when you add or modify features or fix bugs. Because there are so many points to consider, strategic programming takes a certain amount of time to design the modules, but it is well worth the time compared to the problems caused by tactical programming later.

Make the module a little deeper!

A module consists of two parts: interface and implementation. If a module is compared to a rectangle, the interface is the edge at the top of the rectangle, and the implementation is the area of the rectangle (the implementation can also be regarded as the function provided by the module). When the functions provided by a module are certain, the characteristics of Deep module are that the edge at the top of the rectangle is short, and the overall shape is tall and thin, that is, the interface is simple. Shallow modules are characterized by the longer edges at the top of the rectangle and the squat shape of the module, which means the interface is more complex.

The user of a module tends to see only the interface, and the deeper the module, the less information the module exposes to the caller, and the less coupling the caller has with the module. Therefore, designing modules a little more “deep” can help reduce the complexity of the system.

So how do you design a deep module?

  • Simpler interfaces

A simple interface is more important than a simple implementation, and a simpler interface means that the module is easier to use and easier for callers to use. The form of simple implementation + complex interface, on the one hand, affects the usability of the interface, on the other hand, deepens the coupling between the caller and the module. Therefore, when designing modules, it is best to follow the principle of “leave simplicity to others and complexity to yourself”.

Exceptions are also part of the interface. In the coding process, we should prevent the phenomenon of throwing exceptions randomly without processing, which will only increase the complexity of the system.

  • More generic interfaces

When designing an interface, you often have two choices :(1) make it a dedicated interface; (2) Designed as a universal interface. The former is more convenient to implement, and can fully meet the current needs, but the scalability is low, belongs to tactical programming; The latter takes time to abstract the system, but it is highly scalable and belongs to strategic programming. A generic interface implies that the interface is suitable for more than one scenario, typically in the form of “one interface, many implementations”.

Some programmers might argue that generality, in the absence of knowing what will happen in the future, means over-design. Over-generality is overdesign, but a modest abstraction of an interface is not. Instead, it can make a system more layered and maintainable.

  • Hide details

As you design your modules, learn to distinguish between what information is important and what is not to the caller. Hiding details refers to exposing only important information to the caller and hiding unimportant details. Hiding details makes the module interface simpler and the system easier to maintain.

How do you tell if the details are important to the caller? Here are a few examples:

1. Important details about the Java Map interface: each element in the Map is composed of <Key, Value>; Minor details: How these elements are stored underneath the Map, how thread safety is implemented, and so on.

2, for the file system read function, the important details: each read operation from which file to read, read how many bytes; Minor details: how to switch to kernel mode, how to read data from the hard disk, etc.

Important details for multithreaded applications: how to create a thread; Minor detail: how the multicore CPU schedules the thread.

Layered design!

A well-designed software architecture is characterized by clear layers, each of which provides a different abstraction, with clear dependencies between the layers. Whether it is the classic Web three-tier Architecture, the four-tier Architecture advocated by DDD and the hexagonal Architecture, or the so-called Clean Architecture, all have a distinct sense of hierarchy.

When designing in layers, it is important to note that each layer should provide a different abstraction and try to avoid having a large number of pass-through Mehod in a module. For example, in the four-tier architecture of DDD, the domain layer provides the abstraction of the domain business logic, the application layer provides the abstraction of the system use cases, the interface layer provides the abstraction of the system access interface, and the infrastructure layer provides the abstraction of the basic services such as database access.

Pass-through Mehod refers to functions that “call other functions directly in the body of a function, but do very little themselves,” and usually have a function signature that is similar to the signature of the function being called. The pass-through Mehod module is usually a shallow module that adds unnecessary layers and function calls to the system, which makes the system more complex:

public class TextDocument ... { private TextArea textArea; private TextDocumentListener listener; . public Character getLastTypedCharacter() { return textArea.getLastTypedCharacter(); } public int getCursorOffset() { return textArea.getCursorOffset(); } public void insertString(String textToInsert, int offset) { textArea.insertString(textToInsert, offset); }... }

Learn to write code comments!

Annotations are a cost-effective tool in the software development process, taking 20% of the time and getting 80% of the value. It can improve the readability of obscure code; It can hide the complex details of the code. For example, interface comments can help developers quickly understand the function and usage of the interface without reading the code. If written well, it can also improve the design of the system.

For details on how to write good code comments, see How to Write Good Code Comments. The article.

conclusion

Complexity of software is something we programmers must face in daily development. It is an essential ability to learn how to “figure out what is software complexity, find out the reasons that lead to software complexity, and use various techniques to overcome software complexity”. There is an old saying that “quality of code determines quality of life”, and when you reduce complexity, bugs, and maintainability of your software, it leads to a better quality of life.

Modular design is one of the most effective ways to reduce software complexity. Learn to use “strategic programming” and stick with it. We often advocate “getting it right the first time”, but this is not true of modular design, and almost no one can design a module to look perfect the first time. Re-design is a very effective technique. Instead of spending a lot of time refactoring or rewriting the system after it’s corrupted, spend some time re-designing the module after you’ve designed it for the first time and ask yourself: is there a simpler interface? Is there a more universal design? Is there a cleaner and more efficient implementation?

“Rome wasn’t built in a day”, so is reducing the complexity of software.

Click on the attention, the first time to understand Huawei cloud fresh technology ~