Diagnose Java code: Easy code Maintenance

Source: Internet
Author: User
Avoid unnecessary changes and access to make the code robust and easier to maintain
Level: Entry

Eric E. Allen (eallen@cs.rice.edu)
Ph.D. student, Java programming language team, Rice University)
April 2003

This month, Eric Allen explained how it is critical to maintain code robustness while making code easier to maintain while avoiding and controlling changes without reason. He focused on concepts such as functional style code writing, as well as methods for marking fields, methods, and classes to handle and prevent variability. Eric also explained the role of unit test and refactoring in this task and provided two tools to assist in refactoring. Share your views on this article with the author and other readers in relevant forums. (You can also click "discussion" at the top or bottom of this article to visit this forum .)
Effective debugging comes from good programming. Designing a program that is easy to maintain is one of the most difficult challenges for programmers, partly because the program is not usually maintained by programmers who write code. In order to effectively maintain such a program, new programmers must be able to quickly understand the working principle of the program. If the programmer can understand a small part of the program separately, it is easy to understand the working principle of the program.

By discussing variability, decoding, private methods, final methods, final classes, local code, unit tests, and refactoring, we will briefly describe some programming methods, to help make the program easier to understand and maintain.

Variability and Decoding
First, we will discuss the problem of variability. If the data processed by each part of a program is not modified by other and remote parts of the program during computing, it is easy to understand each part of the program separately.

Too much information
For example, consider a program that uses a container instance to modify the component links. Each time you pass the methods of a container from a certain part of the program to other parts of the program, and each time you call a new expression (where the container is passed as a parameter, containers may change from the control of calling methods.

Before we first understand how to modify the container for each method that calls a method, we cannot really ensure that we understand the call method, so our ability to diagnose errors is even worse. If each of these called Methods calls other modification methods in turn, the total amount of code that the maintenance programmer must read in order to understand a single method will rapidly increase, which is beyond control.

For this reason, it is advantageous to use different classes for variable containers and immutable containers. In an unchangeable version, the container field can be marked as final.

Function Style
Compared with Modifying Old data, code writing for constructing new data is called a function style. Because the method of a program is similar to that of a mathematical function, its behavior is described separately based on the output returned by each input.

Function styles are often ignored. It is quite easy to understand individual components of a program. If the data manipulated by a method is never changed by any operation performed by its subject, what the programmer must do is to understand the results returned by those operations. Compare it with the previous method that calls several other methods. The other methods in that solution modify the data structure operated by this method.

A good feature of the Java language is that it allows us to use the final keyword (as a pseudo command of the type checker) to declare when we want to make a data immutable.

Using final keywords to avoid changes is a good way to "pin" The method behavior of the class. Each time you modify a field, it is possible to change the behavior of the method that references this field. In addition, mark the field as final so that other programmers in the reading program can immediately know that no matter how large the program is, never modify the field. For example, consider the following class hierarchy that represents an unchangeable list.

Listing 1. class hierarchy of the immutable list

Abstract class list {...}
Class empty extends list {...}
Class cons extends list {
Private final object first;
Private final list rest;
}

All fields in these classes are marked as final. Make sure the instances of these classes are unchangeable. Is this enough? Not enough. Of course, even if the field is marked as final, the component of the field may not be final. Remember this is important. When those components are changed, any part of the program that references those components may be modified, regardless of whether the field itself is changed. In the above example, although the list composition elements cannot be modified, we must check that those elements themselves do not contain non-final fields that may be modified.

In this case, although the list may contain variable elements, we can see that the sequence of elements stored in the given list is not changeable for the following reasons: Empty list (that is, list with zero length) instances do not contain any elements at all; therefore, they cannot be modified. The cons (non-empty list) instance contains two fields, both of which are final. The first field contains the first element of the list, which cannot be modified. The second field contains a list containing all the remaining elements. If the content of this list is immutable, the included list is also immutable.

However, the list contained in the second field is one shorter than the list containing the list, so if we know that all lists with the length of N are unchangeable, then we know that the list with the length of N + 1 cannot be changed. Because we already know that the list with zero length is unchangeable, we also know that the list with a length of 1, 2, and 3 is also unchangeable.

Tracking similar data structure connections can be boring, but it is worth doing so when you can determine the global characteristics of this structure (such as immutable.

Control Changes
The best strategy to prevent unexpected changes is to avoid all changes as much as possible. We should use it only when there are reasons that must be changed (for example, when this greatly simplifies the code structure. When changes can be avoided, the benefits are enormous (in terms of low maintenance costs and enhanced robustness ).

Even if there is a reason for changing the data, it is best to manage that change to limit possible damages as much as possible. Iterators and streams are good examples of data structures that are clearly designed to allow us to take advantage of a series of elements in a regular, well-defined form, instead of explicitly modifying a handle of these elements to control changes.

Private Method
Just as setting a field to final helps restrict the external impact on the field value, setting them to private helps limit their impact on other parts of the program. If the field is private, we can make sure that other parts of the program are not directly related to it. If we remove this field and replace the internal representation of this type of data, we only need to care about the Internal correction method to correctly access new data.

In the previous example, note that the cons fields are private. In this way, we can use the getter method and similar methods to control how to access those elements. If the future maintenance personnel of our list sometimes want to modify the internal representation of the List (for example, it can be demonstrated that the array-based list may be more effective on some platforms), then programmers can do this, without having to modify or even view any clients in those lists. He only needs to override getter to take appropriate actions on new data.

Final method, final class, and local code understanding
In contrast to marking a field as final, marking a method as final is often accused of being inconsistent with the OO design goal because inheritance polymorphism is prohibited. However, when trying to understand the behavior of large programs, this helps to understand what methods are not overwritten.

A good OO design involves a lot of inheritance, which is indeed a fact. In fact, inheritance is the core of many OO design patterns. But that doesn't mean that we should allow every method we write to be rewritten. Generally, the program will implicitly rely on some key methods that are not overwritten. By marking this method as final, we will allow other programmers to better understand the expression behavior that calls this method.

In addition, marking a class as final greatly improves decoding. It will really help you to get a preliminary idea of which classes in the program will never be quilt. In fact, I think: only classes that should not be labeled as final are actually quilt classes in the program, and classes intended to be categorized from external components (as an inherent part of program design ).

Some may think that this concept will constrain future code maintenance personnel so that they cannot extend the code. I think this will certainly not limit them. If future maintenance personnel of the program need to extend the Code to include child classes that do not previously exist, as long as they have access to the source code (if they do not have access to the source code, so how can we become the "maintainer" of the code ?), It is not difficult to delete the final keywords in the corresponding class and recompile them.

At the same time, the added keyword acts as an automatic verification document form for important constants of the Program ("Automatic Verification" is because if the document is corrupted, the program will not even be compiled ). By forcing developers to consciously choose when to delete such constants, we can help reduce the introduction of errors.

Unit Tests and changes
Unit testing always helps to understand code with side effects. If a unit test fully demonstrates the role of a method in a program, the programmer can quickly understand each method by reading its unit test. Of course, whether unit tests really cover all the functions is a big problem. Effective range analysis tools similar to clover can provide some help here.

However, unit tests are much easier than writing strict function methods. To test strict function methods, all involved is to call these methods with a variety of representative inputs, and check their output (and ensure that they can be thrown when an exception should be thrown ).

When testing the method for modifying the data structure state, we must first perform such operations, which are required to put the input data into the desired state of the method, then, after calling this method, check whether each modification of the data expected by the client is performed correctly.

Encapsulated with refactoring tools
These skills are useful when writing new code, but what do you do when you have to maintain old code that is barely decoded? Reconstruction, reconstruction, or reconstruction.

Although it is time-consuming to refactor the old code, it is worth the time. In particular, all the tools that support refactoring now support Java code. There are already many powerful tools for automatically refactoring Java code, which can automatically save key immutations.

Idea development environment is a fully functional tool for refactoring Java code. This environment provides automatic support for many Martin Fowler refactoring modes. Another very useful tool I found is codeguide, a German IDE. Although the list of automatic refactoring is small compared with idea, it shows an extremely powerful feature-continuous compilation. When you enter the new code, codeguide analyzes it and tells you whether the project is incomplete (of course, this produces a short delay, which prevents an error signal from each key-Press ).

Although continuous compilation has a negative impact on the response, it is worth waiting in some contexts. For example, you can enter final in front of the field to check whether the project is complete. If no, you know that this field is not modified anywhere in the program. Similarly, if you enter private before a field, you can immediately obtain a list of all external accesses to the field (in the wrong form ).

Another outstanding feature of codeguide is its seamless support for JSR-14 lab extensions with generic types (planned to be officially added to Java 1.5 ).

Although it takes a lot of time and effort to write code for decoding, it will help improve the Code's life and robustness, in addition, it can significantly improve the quality of life of those programmers who are faced with code maintenance tasks. Finally, it takes a lot of time to refactor the Old Code to make it easier to maintain, but you will know that this is worthwhile when you must correct the error next time.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.