This article is selected from the "Software Development Scientific and Art" book. The book thoroughly analyzes the ideology and process of Microsoft software development.
Hands fight each other, not stealth
As a software developer, you must test your own procedure, making the code better and more stable. For my personal experience, if there is no test, the program cannot run correctly.
In addition, one thing to do between the developers in the same group is: others check your code, in turn, check the code of others, this process is not only the people who want to check you to discover you. The problem in the code, or you find the problem in others code, more importantly, in the process of explaining your code to others, you can find yourself, and you can post your own ideas.
The following program is a piece of code I wrote when I developed Exchange Server. I didn't test it after I was written. Because this code is too simple, only a few lines of code: Take the length of the file, if an error is returned. So I just submit it (Checkin) to the actual product after compiling.
As a result, when I went to the office next morning, I found that my three boss had already been waiting for me there. It turns out that all Exchange Server can't run! Because my code is added to the Exchange Server boot code sequence, when Server starts, because of the error of my code, it failed, resulting in Doa (DeAd On ArriVal).
.
//
// Get File Size First
//
DWORD dwfilesize = getFileSize (HFile, NULL);
IF (dwfilesize = -1) {
// What can we do? Keep Silent
ErrorTrace (0, "GetFileSize Failed with% D", getLastError ());
Return;
}
Note: When the getFileSize call fails, -1 will be returned.
The error of this code is that the judgment condition of the IF is written to assign, so no matter how it is wrong, then return.
In fact, the improvement method is simple: it is to move -1 to the front. This way, if you miss a "=", compiler will find errors when compiling. So in the IF statement, put constants in front.
//
// Get File Size First
//
DWORD dwfilesize = getFileSize (HFile, NULL);
IF (-1 == dwfilesis) {
// What can we do? Keep Silent
ErrorTrace (0, "GetFileSize Failed with% D", getLastError ());
Return;
}
This lesson is very profound.
See the move, the drip is not leaking
Error case refers to errors that are not easy to reproduce. Be sure to handle the error situation, which is from the crash. The most commonly occurring cases are as follows.
× memory consumption. Don't think 100 b is small, not wrong. Because when the system is running, although our own program applied, it is very small, but it does not guarantee that the memory of others' program is very small. This often occurs during the development process: there are too many memory applications for others, causing my programs to crash due to memory applications. × anomaly. An abnormality often occurs in C , and it is necessary to do it. Users who have used MFC know that if an exception occurs, it must handle it, otherwise the program will crash at any time.
× Network interrupt. Don't think that sending a socket will succeed. Especially use asynchronous sockets to make some network software, the client is easily erroneous when sending the packet to the server. Since it is asynchronous, although the sender is correct, it may be wrong for a period of time, and this asynchronous error is difficult to detect. Some errors can be generated by means of some means during the debugging process, and some errors cannot be reproduced. In the case where the error cannot be reproduced, we have to do some tools to force the error.
In addition, you should pay attention to the process of programming:
× Do not do some operations that may fail during the constructor of C , such as memory operation, because there is no way to know after an error in the constructor.
× Treatment error When you want to release the assigned resources; the behavior of the program should be clearly defined, such as return status, abnormal processing, etc., let the caller clearly know the definition of the interface.
× Do not ignore the error, causing the program to crash or exit.
CWINFFILE :: CWINFFILE () {
M_PLLINES = New TPTRLIST (); // ...
m_plsections = new TPTRLIST (); // ...
m_readcontext.posline = m_plines-> end ();
.
}
The biggest problem in this program is the MFC's new operation, and if it fails, it will produce an exception. If the previous NEW operation is allocated, while the next NEW assignment error, the allocation of the previous variable can cause the memory of the memory. The solution is to handle with a try ... catch statement. If an exception occurs, the variable is deleted, and the memory is released.
CWINFFILE :: CWINFFILE () {
Try {
M_PLLINES = New TPTRLIST (); // ...
m_plsections = new TPTRLIST (); // ...
m_readcontext.posline = m_plines-> end ();
.
} catch (...) {
IF (m_pllines) delete m_plnes;
IF (m_plsections) delete m_plsections;
}
}
However, there is still a problem in this: Since the constructor is the first execution, if the new assignment error is wrong, the DELETE statement is executed, while the variables m_plnes and m_plsections have not been initialized, and if they perform DELETE operation, it will be wrong. So we should initialize it in the constructor.
CWINFFILE :: CWINFFILE (): M_PLLINES (NULL), M_PLSEctions (null) {TRY {
M_PLLINES = New TPTRLIST (); // ...
m_plsections = new TPTRLIST (); // ...
m_readcontext.posline = m_plines-> end ();
.
} catch (...) {
IF (m_pllines) delete m_plnes;
IF (m_plsections) delete m_plsections;
}
}
Note that if you are not MFC, the new operation will return null without throwing an exception, then the initialization of the third variable will be wrong.
CWINFFILE :: CWINFFILE (): M_PLLINES (NULL), M_PLSEctions (NULL) {
Try {
M_PLLINES = New TPTRLIST (); // ...
m_plsections = new TPTRLIST (); // ...
m_readcontext.posline = m_plines-> end ();
.
} catch (...) {
IF (m_pllines) delete m_plnes;
IF (m_plsections) delete m_plsections;
}
}
The following is not to use some operations that will fail in the constructor.
Class foo {
Private:
Char * m_pszname;
DWORD M_CBNAME;
PUBLIC:
Foo (char * pszname);
Char * getname ()
{return m_pszname;}
...
}
Foo :: foo (char * pszname)
{
m_pszname = (byte *) malloc (name_len);
IF (m_pszname == null) {
Return;
}
STRCPY (m_pszname, pszname);
m_cbname = strlen (pszname);
}
......
Foo * pfoo = new foo ("myname");
IF (PFOO) {
CHAR C = * (pfoo-> getname ());
}
Since the error code cannot be returned in the constructor, when we create a Foo object using the New operator, it calls its constructor. If the Malloc function is wrong, the constructor returns directly, then the PFOO does not assign the value, so that it is behind Access to PFOO in the program will be wrong. Therefore, do not use some operations that will fail in the constructor.
In the following version, the constructor no longer contains a failed ingredient, but an init function is added to complete the memory allocation operation. Call the new function in use, call the init function, check the return value, determine if it is correct, and return it.
Class foo {
Private:
Char * m_pszname;
DWORD M_CBNAME;
PUBLIC:
Foo ();
HRESULT
INIT (CHAR * PSZNAME);
...
}
Foo :: foo ()
{
m_cbname = 0;
m_pszname = NULL;
}
HRESULT foo :: init (char * pszname)
{
HRESULT HR = S_OK;
IF (pszname) {
m_cbname = lstrlen (pszname);
m_pszname = (char *) malloc (m_cbname 1);
IF (m_pszname == null) {
HR = E_OUTOFMEMORY;
Return HR;
}
STRCPY (m_pszname, pszname);
} else {
HR = E_INVALIDARG;
}
Return HR;
}