Development of complex software systems is fraught with considerable difficulties due to the need to:
a) quickly create a prototype system,
b) ensuring the quality of its model and source codes,
c) making changes throughout the life of the system.
The following are ten recommendations that will help developers on their difficult path of creating integrated systems.
1. Harness the power of modern software development platforms')
Do not focus on development platforms that are outdated or tend to become obsolete at the time you start building your system.
And do not be afraid of the need to relearn new languages ​​and APIs. Serious systems require appropriate training of developers. You will more than return the time spent on training by using more powerful tools and class libraries. Progress still does not stand still ...
2. Be serious about designing an application.Creating a future system architecture is a mixture of experience and intuition. It is difficult to distinguish a set of rules, following which guarantees the creation of a qualitative model of the system. The main thing is that the architectural background of your system does not reflect the domain in an unnatural way.
The success of the future project can be indirectly judged by the number of times when at the design and implementation stage the phrases were said “do it now and then, if anything, redo it” (more worse) and “then we want this block of functionality and we realize "(more is better). Why? Because in the first case, an explicit assumption was made about how the functionality block should work, and moreover, to work in the “crutch” mode. And in the second case, the decision on a specific implementation was postponed to the stage when it will be clear how it should look.
3. Use inheritance of domain objects and user interfaceWhen using inheritance, you can set “in one place” the general behavior for a group of objects of your system.
For domain objects, inheritance will help you in defining common attributes and functionality of objects.
For the user interface, inheritance will be useful when defining common components of screen forms (be it win or web forms).
As an example of the inheritance of representations, consider the form of an abstract archive (that is, a list) of documents. In accordance with the inheritance hierarchy of business objects, an abstract document has 2 predefined attributes “Number” and “DateDocument”. Let's draw the form of an abstract archive of documents and place on it a table (“grid”) with two columns: “Number” and “Date”. We indicate in the upper part of the form the components of the archive depth control component (the initial selection of records by the document date) and configure the table context menu buttons.
Next, we define the object "Frame Document", which is inherited from the "Abstract Document". In addition to the “Number” and “Document Date” fields, the new object will have the “Employee” field. At the same time, you do not need to “draw” screen forms for a new archive. You specify the abstract archive form as the base for the new archive form, add the “Employee” column to the existing table and link this column with the data. All the functionality of the form (for example, the depth of viewing) will be inherited and automatically implemented in the archive of personnel documents. Reducing development time is obvious.
4. Separate application business logic and user interfaceFulfillment of this condition actually means the appearance of a “contract” of interaction between the GUI and business logic. In this case, you can not only provide your system with several user interfaces (for example, win and web), but also implement secure and understandable interfaces for business logic classes.
This “contract” will help you in the future when making significant changes to the system model.
5. Your system should be a set of business-level objects interacting with each other , unless nuclear explosions are modeled using it, of course
This is the basic principle of an object-oriented approach to software development. You write classes for domain objects and force them to exchange messages (call open methods on each other). Thus, the required functionality of the system is achieved.
For example, you need to save the edited object in the database in the screen form when you click the "OK" button. The code for your button click handler should look like this:
{
EditableObject.Save ();
}
The Save () method in itself contains all the necessary checks for the correctness of the object, the generation of the corresponding object event and the transfer of control to the database manager of the save parameters. That is, the display object sends a message with a “Save” signature to the associated business object.
And if in the specified example you check the correctness of an object into a screen form method, then such code will be difficult to control in the future. Especially if the object has several editing forms.
6. Encapsulate the functionality of your system into objects, components and subsystems.A set of classes that work together to achieve well-defined functionality must encapsulate into a component and declare an interface (that is, a set of agreements and message signatures) to interact with it.
Later, if the interface remains unchanged when you make changes to the system, you can absolutely safely rework the internal behavior of the component without affecting the rest of the system components. However, to achieve such an advantage, it is necessary to work very seriously on a system model (see Tip 2).
7. Use clear, understandable and natural interfaces to describe the interoperability capabilities between components and subsystems of your application.
Interfaces are the link between the components of the system. The modifications of the component that do not change its interface-interactions do not affect the operation of the system as a whole and do not require comprehensive integration testing of the system.
Thus, the main goal of the developer is to provide your component with such an interface so that it remains unchanged as long as possible in the process of finalizing the system. If this interface is not clear, understandable and natural, then with a high probability it will be changed in the very near future (see Tip 2).
8. Write automatic tests to verify the correctness of the implementation of the functionality of the system.Automated tests help the developer to understand if its component is working correctly. Each test script is responsible for checking the individual component interface element (the so-called unit test). For writing such tests there is an open source development of NUnit.
Tests are divided into positive and negative. Positive tests are aimed at finding out whether the component works correctly on the correct set of input data. Negative tests track the correct response of a component to a set of invalid input data. As you understand, both types of tests should be presented in your test component driver.
In some cases, a test driver component is recommended to write before the development of the functionality of the component itself (for a set of use cases). This will help you in developing the “right” interface of the component.
If you managed to write a set of test drivers for your system with good coverage of test cases, then at the stage of making changes to the system code you will save a lot of time.
9. Use automatic code generation tools.
In large systems, the proportion of code that can be obtained without the participation of a qualified developer is large enough to write by itself or use existing means of automatic code generation.
This way you will save yourself from unnecessary unnecessary errors when writing code, and this will allow you to concentrate on the implementation of non-trivial functionality of the system.
10. Remember that a database is not a goal, but a meansWhen choosing which database server to use for your system, it is necessary to evaluate not the simplicity of writing code to interact with it, but its security, speed of operation, data integrity and other parameters. At the very least, you can write “wrappers” for performing interaction operations with your DBMS, but not changing the behavior of the server itself.
It is also necessary to approach the question of writing stored procedures to perform operations with the database server. It makes no sense to write all the business logic of the system in the form of stored procedures. In this case, you will not be able to effectively use an object-oriented approach to developing the system and distribute the computing power between the computers of the local network. In stored procedures, it makes sense to make only really "heavy" processing, affecting a large amount of data.