At one time, I was entrusted with the task of supervising young developers. It was decided to start with the formulation of brief recommendations. The result is in front of you.
Write a code that is convenient to read for everyone else.
Write in the comment not WHAT the code does, but WHY
Warnings and hints are more dangerous than compilation errors - the project is being built without their removal.
2 development cycle
Task setting by the head
Decision making
Solution Review
Solution implementation
Code Review
Placement in the version control system
3 Making the source code
Careful adherence to proprietary agreements in this matter should be followed - to the last comma, regardless of how convenient or logical they appear to the programmer. We all work in a team, and confusion in the design worsens the main thing - the readability of the code.
An incomplete code will force the author to spend the extra time (on correcting shortcomings) and the reviewer (making a list of defects and retesting after correction), so the best way to save time on registration is to do it right the first time.
4 Comments
The first line of the comment before declarations of procedures, classes, etc. should make their appointment clear. Subsequent lines describe specific implementation features.
Comments to virtual methods should describe the circumstances of the call, not the implementation - it can be overlapped in the heirs.
Comments in the body of procedures and methods should not describe WHAT an operator or block does, but should indicate the AIM for which it (the operator) is used.
Goals change much less frequently than the means of its implementation
It is possible to understand what the code does out of it, to understand why the code is unrealistic.
5 Choice of names
Classes are the singular noun with the obligatory prefix T and optional in the form of a company name (SMS) if there is a library class with a similar name and a project (ZVK, Plan) if the class cannot be involved in several of our projects.
Interfaces - nouns with the prefix I
Procedures - the verb.
Functions - the noun of the singular when returning values ​​and the plural - the collection. Use the Get prefix for methods of reading properties and functions intended for receiving external data, the Is prefix for logical functions, the Create prefix for creating functions. Functions that are called to perform an action (and not just get the result) are called as procedures, since they are, in fact, de facto.
Properties is the singular noun, except for properties-arrays. For array properties with an integer index, the satellite property is defined with the suffix Count. The event property must be prefixed with On, Before, or After. Methods for reading and writing properties have the required prefixes Get and Set.
Virtual methods are a verb with the prefix Do for protected and without the prefix for public methods.
The name of the virtual method should reflect the purpose of its call, not the implementation.
6 Operators
The depth of the nesting of operators into each other should be as small as possible - a large depth greatly degrades the readability of the code.
To reduce the nesting depth of branching, it is useful to use transition operators (except goto) and raise
To reduce the embedment depth of try..finally constructions, it is useful to use assignment nil to tryable objects to be cleaned.
7 Encapsulation
You should not use existing global variables unless absolutely necessary and you should never add your own
All class fields are private only.
One class should perform one task, and not combine in itself the storage, display, editing and writing to the database
Access to fields for descendants and outside the class can be done through the properties
All methods that implement the interface are necessarily protected and non-virtual.
Everything that is not used outside the module is in implementation.
If the use of the class draws most of the project, you should write an interface for this use, implement it in the class and use only the interface
8 Error Handling
Only exception should be used for error handling.
Exceptions should only be used for error handling.
In the except blocks, you should raise all exceptions that are not handled explicitly.
Categorically should not "swallow" exceptions (except end)
Categorically you should not throw exceptions to the base class Exception: if there is no suitable library, you need to define your
Do not handle exceptions only for writing to the log - this is done automatically.
Any code must be written taking into account the fact that an exception may occur in its any place.
Always raise an exception in case of errors in the constructor and never excite or catch exceptions in the destructor - the destructor should always exit correctly.
The possibility of leakage of ONE of any resource used within the subroutine is eliminated using try..finally, while the resource is captured immediately before try, and the release is finally received.
The possibility of resource leakage in the creating (exciting) function is eliminated with the help of try..except, while the resource is captured before try and released in except with the subsequent raise.
The possibility of resource leakage in the constructor is eliminated by storing references to the captured resources in the fields of the object and by taking into account and writing the destructor correctly deleting the partially initialized object. This is related to the order of creation of the object in Delphi.
Memory is allocated for the object and filled with zeros.
The constructor is running.
If an exception is raised in the previous step, the destructor is called, the occupied memory is freed and the exception is raised.
If resources are object references, then in a destructor it is enough to use FreeAndNil for them.
Within the subroutine, you can use the same technique instead of nesting blocks of try..finally blocks, placing the “constructor” immediately after try and “destructor” in finally. Since local variables are not initialized, this must be done manually, assigning them nil before try.
If a resource is not implemented as a class and is used repeatedly, it is necessary to create a class in the constructor of which the resource is captured, and in the destructor it is released. This will make the use of the resource more simple and consistent, and catching its leaks will be reduced to catching memory leaks.
If you need functionality of one class when implementing another, the easiest way is to use a composition, for example, add a reference to an object in the list of class fields. Restrictions - the new class has access only to the public members of the old, it is necessary to write the delegating code.
If the existing class already implements some of the new features, then inheritance can be used. Restriction - a descendant class must completely replace the parent class in all situations - for example, for a TPersistent heir, a correct implementation of Assign is necessary.
If you need to use the new class in the same way as the existing one, but the behavior is required is completely different, then you need to write an interface, which you can implement in both classes. As a result, classes can inherit completely different ancestors, but from the side, they are used the same way through the interface. Restriction - the behavior through the interface is not inherited.
If it is necessary to expand the behavior of the ancestor class (and thereby give new opportunities to all descendants at once), without interfering with its code, then you can write a helper. Restrictions - no virtual methods and new fields, one helper per class within the project.
Do not use helpers unless absolutely necessary
12 Interfaces
Do not worry about leaks when using interfaces.
Methods that implement the interface should be non-virtual and non-public, usually protected.
Any interface contains at least three methods - QueryInterface, AddRef and Release.
These methods are invoked implicitly: the first is cast to a link to an interface with the as operation, the second and third are used when creating a new link to the interface and deleting (or leaving the field of view).
When implementing interfaces to TObject descendants, it is necessary to implement all three of the above methods.
The descendants of the following classes have default default QueryInterface, AddRef, and Release implementations:
TInterfacedObject implements automatic deletion when there are no links to the interfaces of the class, so its descendants immediately after creation should be brought to the links to the interface.
TComponent ignores references to the implemented interfaces and is removed only manually, so care should be taken that after deleting its descendant, there are no hanging links to the interfaces.
13 Visual components
Frames and forms should not contain any logic, except the logic of display and transfer (not execution!) Of user commands.
All possible user command options must be formatted as an Action and placed in the same ActionList in the same frame in which this command is available.
The names of components that were created automatically by Delphi can only be left for those that are not referenced in the method code and there are no handlers. A meaningful name must be set before creating the first handler for this component, otherwise, then you will have to edit the name of the handler manually.
Automatically created handler names should be changed only when absolutely necessary.
Event handlers for visual components should not take more than five lines. Comments to handler methods should explain not just the circumstances of the call (they can be seen from the name of the handler), but first of all its purpose - why this handler was needed at all.
14 Modules
A module used in more than one project - a library module.
A module that contains nothing but constants, interfaces, types (not classes), exceptions, and auxiliary procedures is an interface module.
The module containing the code for exchanging data with the user, the database file system. network and TP - I / O module.
All other modules are logic modules.
The ideal structure of inter-module dependencies: the interface modules depend only on the interface modules; Logic modules - only from interface and other logic modules; I / O modules — only from interface and other I / O modules. This allows you to effectively use unit tests for logic and change the implementation of different parts of the project independently of each other.
15 Eliminating unnecessary dependencies
A public global variable is the worst kind of dependency, getting rid of at least by converting a variable into a function
From a private global variable is slightly better, since it localizes the problem within the module. It is necessary to monitor the possibility of multithreaded modification and initialization-termination. For example, for global interface variables, when module is finalized, Release is implicitly called.
From the interface module, you do not need to change anything unless absolutely necessary.
From global classes, procedures and functions to any input-output (GUI, database, connection to an application server, etc.) of other modules. If the project modules themselves are intended for I / O, then nothing needs to be changed, otherwise it is desirable to get rid of it by implementing this connection through the interface — otherwise, it will be extremely difficult to make changes and test.
From the modules containing the above - it is better to get rid of, selecting from the module to which there is a link, the interface part and referring only to it.
From any contents of a library or shared module - without extreme need, you don’t need to change anything.