To ensure the stability and success of the architecture, it is a practical method to verify the architecture by using code. The core of code verification is testing, especially unit testing. The basic operating principle of testing is testing first. It is a very important practice in Agile Methods and an important guarantee for reconstruction and stability of core models.
Code verification in the object-oriented system
Code verification is a way to ensure excellent architecture design, and also a way to avoid the emergence of ivory tower architecture design. In the previous article, we mentioned in stability that architecture design will eventually be embodied in the form of code. Therefore, it is most effective to use formal code to verify the architecture.
Because it is code verification, it is inseparable from coding, and the code is always closely related to the specific language and compiling environment. Here we mainly discuss the object-oriented language, the Java language used in the sample code. There are many advantages of using object-oriented languages for Architecture Design:
First, object-oriented language is a better structured language. Compared with non-object-oriented languages, it can better implement encapsulation, reduce coupling, and allow designers to think at an abstract level. These factors provide conditions for excellent architecture design.
Second, the object-oriented language allows designers to focus only on the Framework Code, rather than the specific implementation code. Of course, this does not mean that non-object-oriented languages cannot do this, but the performance of object-oriented languages is better.
Finally, object-oriented languages can be well reused. This means that designers can use their original knowledge and original software systems to solve new problems.
In addition, more benefits can be obtained by using the Java language. Java is an interface-oriented language. We know that the Java language itself does not support multi-integration, and all Java classes are inherited from the Object class. In this way, it is difficult to change an inheritance system once it is determined. To achieve the flexibility of Multi-inheritance, Java introduces the interface mechanism. There is no difference between using interfaces and using abstract classes. A specific class can implement multiple interfaces, the client can use it by declaring the interface type, as shown below:
List employees = new Vctor ();
If you need to replace Vctor with a mirror list, you do not need to modify any other Code except the above Code. In addition to the List interface, the Vctor class also implements Cloneable, Collection, RandomAccess, and Serializable. This shows that in addition to the List interface, we can also access the Vector class through the interfaces listed above. Therefore, interface inheritance can become a complementary means of class inheritance and play a very flexible role. At the same time, the complexity of Multi-inheritance is avoided. However, only empty methods can be defined in the interface, which is a defect of the interface. Therefore, in actual programming, interfaces and abstract classes are usually used together. In Java. util, we can see the Collection interface and the AbstractCollection abstract class that implements the Collection interface. You can inherit from the AbstractCollection abstract class (or a subclass of it), so that you can use the default code implementation in AbstractCollection. Because AbstractCollection implements the Collection interface, your class also implements the Collection interface. If you do not need to use the code in AbstractCollection, you can write a class by yourself to implement the Collection interface (this is unlikely to happen in this example, because the reusability of tool classes has been designed very well ). There are many similar examples in Java. Java language design is not the focus of our discussion. for more in-depth discussions, see special books. We will not introduce it too much here.
It took some time to discuss some simple preparations for Object-Oriented Design and interface-oriented design. This knowledge will become the basis for code verification.
Interfaces and architecture
The Interface here is not the Interface concept in Java. It is a generalized Interface, which is embodied in the public method or Interface method of the class in Java. There are similar but not identical performances in the COM or J2EE systems. For a system architecture, the most important thing is to define these interfaces. These interfaces are used to link system classes, provide services to users through interfaces, and connect external systems (such as databases and legacy systems) through interfaces ). Therefore, to verify the architecture, we need to convert it into interface verification requirements.
The basic idea of Interface Verification is to ensure the interface testability. To ensure that interfaces are testable, you must first analyze the responsibilities of classes and classes. There are several principles to improve the interface testability.
1. encapsulation principles
The Implementation Details of the interface should be encapsulated inside the class. For a class user, he only needs to know the public method released by the class, rather than the implementation details. In this way, you can write the corresponding test code according to the common method of the class. As long as the test code is satisfied, the class design is successful. For the architecture, the testability of the class is the foundation, but it is not enough to ensure it.
2. Minimum Responsibility Principle
The question of how many functions a class (Interface) needs to implement is a constant debate. However, the functions implemented by a class should be as compact as possible. A class only processes closely related functions, and a method should only do one thing. In this way, the test code of the class is also relatively concentrated, ensuring the testability of the class. Recall the example we discussed in the layered mode. The implementation class provides different interfaces for different users, which is also a embodiment of the Minimum principle.
3. Minimum interface Principle
You must be cautious when releasing the method to users. Generally, release methods should be as few as possible. Since published methods may be frequently used by customers, existing methods may be affected if there are design problems or the design needs to be improved. Therefore, these effects must be minimized. On the other hand, some lightweight common methods should be combined into a single method. This can reduce the coupling between the user and the system. The specific method can be through the appearance mode or the business delegation mode. For more information, see hierarchical mode. A small number of interfaces can reduce the test workload and make the test work more concentrated.
4. Minimum Coupling Principle
The minimum coupling principle is that the interactions between classes and other classes should be minimized. If a class has a coupling relationship with a large number of classes, a new class can be introduced to weaken the coupling. In the design mode, both the mediation mode and the appearance mode are similar applications. For testing, especially unit testing, the ideal situation is that the testing class is a pure class and has no relationship with other classes. But in reality, this type is very rare, so what we can do is to minimize the coupling between the test class and other classes. In this way, the test code is relatively simple, and the class has little impact on the test code when it is modified.
5. Layered Principle
The layering principle is the improvement of the encapsulation principle. A system usually has various responsibilities, such as the code that is responsible for dealing with databases and the code that is used for dealing with users. By dividing the code into different layers based on functions, You can encapsulate different parts of the software architecture. The class testability guarantee should be developed into the architecture testability guarantee. You need to use the hierarchy principle for the system and write test code at the layer level. For more information about layering, see layering mode.
If the architecture you designed cannot meet the above principles, you can reconstruct the architecture to improve it. For details about refactoring, refer to Martin Fowler's refactoring book and Joshua Kerievsky's refactoring to pattern book.
What is the significance of a verifiable architecture if we look into it? This is the test-driven and automated testing concept mentioned in the next section.
Test driver
The concept of test-driven may be unfamiliar to everyone. Test-first design is the same concept in RUP, while test-first programming is used in XP ). In fact, in our daily work, we are already unknowingly working on the test-driven part, but increasing the test-driven height is attributed to agile methods. The basic idea of test-driven is to consider (or write) the test code before designing (or coding). In this way, the test is not just a test, but a design (or code). Martin Fowler is called "specification by example"
In the field of agile testing. One way is to fully express the requirement as a test code. In this way, the demand of software designers is no longer how to write requirements to capture users' needs, but how to write tests to capture users' needs. This has obvious advantages. The most critical code in software design is that the Code cannot meet the requirements during testing. There are many reasons for this, but the results are terrible, it will lead to a lot of rework. The requirements are organized into the form of test code. The final code can meet the requirements as long as it can be tested. Of course, there must be a premise that the test code should be able to fully and accurately describe the requirements. It is not easy to achieve this. We can imagine that there is basically no code or even no design drawing when we analyze the user's needs. At this time, it is difficult to write the test code. This requires the designer to write the test code, and the overall architecture of the system has become very simple. Therefore, although this technology has bright prospects, it is still far from mature.
Although we cannot fully use the above technologies, it is entirely possible to borrow the ideas.
First, the idea of replacing requirements with test code is good because the test code is unambiguous and can describe the requirements very accurately (because the code level is the finest level ), and closely integrated with the architecture. Therefore, from the demand analysis stage, we should try our best to maintain the testability of the requirement documents. One possible method is to use CRC technology. CRC technology can help designers analyze key classes in requirements and identify the relationships between classes and responsibilities. Similar technologies are also available in RUP. Business entities represent some entity classes in the field. defining the responsibilities and relationships of business entities can also help improve the testability of design. No matter which method, the idea is to use analysis technology to identify and refine key factors in the business field.
Secondly, the test driver believes that testing is more than just testing. More importantly, testing has become a contract. Guides design and testing. In this regard, Bertrand Meyer has long proposed the concept of Design by Contract. From the smallest unit of software design, this contract actually defines the interface between the producer of the class and the consumer of the class.
Finally, if all relevant personnel in the software development team can understand the architecture test code, it will be helpful for the design, implementation, and improvement of the architecture. Here is a question about the responsibilities of testers. In general, we think that the main responsibility of testers is to identify errors. The problem is that testers spend a lot of time identifying errors that developers should not make. Testing is undoubtedly an important part of modern software, but if the daily work of testers is flooded with errors that can be avoided, the quality and cost of the software will be lacking. A good tester should focus on the availability of the software, including whether the requirements are met, whether the specifications are met, whether the design is defective, and whether the performance is good enough. In addition to discovering defects (note that we use defects instead of errors here), The tester should also find out the cause of the defects and give correct suggestions.
Therefore, it is better to require developers to perform code-level tests on the software. Therefore, it is an effective way to improve the software quality to give the architecture test code and require that the Code pass the test. After learning about the test-driven idea, let's answer the question at the end of the previous section. The biggest benefit of verifiable architectures is the ability to build an ever-improving architecture through automated testing. In the restructuring model, we understand the significance of restructuring to the architecture, while ensuring the testability of the architecture, and establish a testing network for it (discussed in the next section ), this is the basic guarantee for the smooth restructuring of the architecture. We know that the basic meaning of refactoring is to adjust the internal structure without affecting the code or external behavior of the architecture. However, once the code is adjusted, it is difficult to ensure the immutability of its external behavior. Therefore, automated testing is equivalent to the external behaviors of the architecture. No matter how the architecture evolves, as long as the test passes, the external behaviors of the architecture remain unchanged.
Interface Testing
As in the previous article, the interface concept is still a generalized interface. We hope that the architecture can maintain the stability of external behavior during restructuring. But it is not easy to do this. To ensure the stability of released interfaces, designers must have rich design experience and field experience. One of the meanings of the minimum interface principles mentioned above is that the more published interfaces, the more troubles it will bring in the future. Therefore, when designing architecture and design classes, we should start with designing their interfaces, rather than thinking about the specific implementation. This is a big difference between object-oriented thinking and process-oriented thinking.
Here, we need to review the methods mentioned in the stabilization model to find immutating factors from changes. The methods described in the stabilization mode also apply to this mode. Only when the interface is stable can the test script be stable and the test automation can proceed smoothly. Encapsulating the changing factors is the main idea to keep the test script stable. The changing factors and the degree of encapsulation vary depending on the environment. For a project, the database is generally fixed, so the data access code can meet the changing needs as long as it can be concentrated in a fixed position. However, for a product, data access needs to be encapsulated into a data access layer (OR an OR ing layer), and the Connection can be dynamically replaced for different database designs.
Test Network
The last concept in this chapter is the concept of testing the network. If software development is carried out in strict accordance with the test-first approach. When the software is complete, a network composed of a large number of test scripts will be generated. Why is it a test network? Test scripts package the software, and the changes in any part of the software will be immediately reflected by the test network. This is like spider web, which can quickly and effectively manage changes to requirements and designs.
The scripts of the test network are mainly composed of unit tests. Therefore, in addition to programming, developers also need to weave and patch the network. The purpose of knitting is to write the test code before writing the code. The repair means that the test script needs to be modified synchronously when the interface changes due to software changes. The extra work seems to increase the workload of developers. However, in our daily practice, we find that the opposite is true. In the first place, although the development speed decreases due to the construction of the test network, it is in the middle of the development process, the cost saved by the change of the test network software can quickly offset the initial investment. In addition, with the familiarity and recognition of the testing priority method, the cost of building a testing network will continue to decrease, and the advantage will become more and more obvious:
It is easy to detect code with errors, removing the worries of developers and enabling them to continuously develop new functions. In addition, it is also the basis for daily code creation.
It saves a lot of time for testers and enables testers to focus on more efficient areas.
In addition, there is an additional cost for creating a testing network. If the development team is not familiar with the object-oriented language, changes to the testing network due to unstable interfaces will increase the construction cost.
Summary
From the above discussion, we can see that the architecture and code are inseparable, and the architecture cannot be called a good architecture without code. This is determined by the purpose of the architecture. The ultimate goal of the architecture is to become executable code, while the architecture provides structural guidance for the code. Therefore, it is an effective way to verify the architecture with code. It is not easy to implement this practice. We need to consider code-level architecture knowledge (although the knowledge we discuss is limited to object-oriented language, but similar ideas can also be found in other languages) and used to serve the architectural design.
(To be continued)
Author profile:
Lin Xing, Senior Project Manager of the Project Management Group of Chen Xun software studio, has many years of project implementation experience. Chen Xun software studio is committed to the application of advanced software ideas and software technology. Its main research direction is software process ideas, Linux cluster technology, OO technology and software factory model. You can contact him by email iamlinx@21cn.com.