Hello, C ++ (29) The two-boat function is not the basic rule of function 5.4 function design. The two-boat function is 5.4.

Source: Internet
Author: User

Hello, C ++ (29) The two-boat function is not the basic rule of function 5.4 function design. The two-boat function is 5.4.
5.4 Basic Rules for Function Design

A function is the basic functional unit of a C ++ program. Just like a brick, a house can be built on a regular basis, and a function can be organized into a program by rule. While using a lot of functions designed by others, we are also designing a lot of functions for ourselves or others. A well-designed function with clear concepts and clear responsibilities makes it easy to use and can greatly improve our development efficiency. In turn, a poorly-designed function is not clearly defined, not only difficult to use, but sometimes even leads to serious errors. Function design has become an important criterion for evaluating the level of a programmer. With regard to function design, the industry has accumulated a considerable amount of experience and rules. These empirical rules should be understood and followed by every new programmer, and must be flexibly applied during development activities. The two basic elements of a function are the Declaration and definition of the function. The following describes the best practices from these two aspects.

5.4.1 design rules for function declaration

The declaration of a function, also called a function interface, is the interface on which a function interacts with the outside world. Like a label on a function box, you can use this label to understand what function is encapsulated in the box, what input data is needed, and what results can be returned. In other words, as long as you know the declaration of a function, you know how to use this function. A large number of practices show that whether a function is easy to use is often determined by the quality of its interface design. When designing and implementing functions, not only must the functions of the functions be correct, but also the interfaces of the functions be clear and readable. Only in this way will the function functions and input/output parameters be clearly understood when using this function, so as to use this function correctly. If the function interface is unclear, the function may be incorrectly used.

Generally, the following rules should be followed in the design of function interfaces.

1. Name a function in the form of "Verb + noun"

A function is an encapsulation of a relatively independent function. A function is usually expressed as an action and a corresponding target object. For example, the "Copy string" function is composed of the "copy" action and the object "string" of the action. Therefore, in order to better express the functions of a function, when naming a function, it is best to use a combination of the main actions of the function and the target object to form a dynamic object phrase, make functions clear at a glance. For example:

// Calculate the Area // Get is the action, and Area is the action object int GetArea (int nW, int nH); // copy the string // cpy is the action, str is the action object char * strcpy (char * strDest, const char * strSrc );

2. Use a complete and clear parameter name to express the meaning of the Parameter

The parameter indicates the object of the function and the data to be processed, that is, the object of the action represented by the function. Therefore, it is best to use a complete and clear parameter name to clarify the specific meaning of this object. If a function does not have any parameters, it is best to use void to fill in, indicating that this function does not require any parameters. The same function. parameter names in different forms can have different effects, for example:

// The parameter has a clear meaning. // You can clearly understand that the first parameter represents the width and the second parameter represents the height, // The function of the entire function is to set the void SetValue (int nWidth, int nHeight) for the width and height values. // The parameter has an ambiguous meaning, // you can only guess the meanings of these two parameters, So that you cannot understand the void SetValue (int a, int B) function of the entire function. // recommended Interface Design, use void to fill int GetWidth (void) if there is no parameter );

3. The order of parameters should be reasonable.

In some cases, the order of multiple parameters indicating specific meanings already has a common rule in the industry. For example, to copy a string function, the target string is always the first parameter, the source string is used as the second parameter. We should gradually become familiar with and abide by these Rules, instead of breaking them in an unconventional way. For example, write a function to set the rectangle parameter:

// Void SetRect (int nRight, int nBottom, int nTop, int nLeft) interface design that does not follow the parameter sequence rules );

The function name of the SetRect () function is a good expression of the function. The formal parameter name clearly expresses the meaning of each parameter, but it is not a good interface design, because the order of parameters does not comply with industry rules. If the function has been written for use by others and others call the function according to common rules in the industry, the function may be incorrectly used due to Parameter order problems. Here, the standard Parameter order should be:

// Standard interface sequence -- first X and Y in the upper left corner, and then X and Yvoid SetRect in the lower right corner (int nLeft, int nTop, int nRight, int nBottom );

4. Avoid having too many parameters in the function

Although C ++ has no limit on the number of function parameters, the number of parameters should not be too large and should be controlled within five. If there are too many parameters, it is easy to make a wrong type or order of parameters during use, which makes it difficult to use functions. If you really need to pass multiple data to the function, you can use the struct to package multiple data, and then pass the entire struct instead of passing multiple parameter data. For example:

// Create the font function HFONT CreateFontIndirect (const LOGFONT * lplf );

Here, the LOGFONT struct package the data required to create a font, such as the font name and font size. By passing a LOGFONT struct pointer, you can use this pointer in the function to access multiple data packages it wraps, which is equivalent () the function passes multiple parameters required to create a font.

5. Use appropriate return values

The return value of a function represents the type of result data returned from the function. If a function returns result data through the return value, the type of result data is used as the return value type. Sometimes the function does not return result data through the return value. If the return value is not required, you can use the void keyword as the return value type. However, to increase the function availability, we sometimes append a return value of the bool type to the function to indicate whether the function is successfully executed. If the function return value is used for error processing, the returned value must be clear and accurate. Some special values, such as 0,-1, or nullptr, are used, it can also be a custom error type number.

Good functions follow the rules described in Figure 5-9.

Figure 5-9 five practices of excellent functions

5.4.2 function Body Design Rules

The function interface design determines whether the function is easy to use. Whether the function can be used or not depends on the design and implementation of the function body. Although different functions are implemented in different functions, function subjects are also quite different. However, there are still some universally applied experience rules for us to learn and reference, so as to design excellent function bodies.

1. Check the parameter validity at the "ENTRANCE" of the function body.

Some functions have specific requirements on parameters. For example, if you set an age function, the parameter indicating age cannot be a negative number. The parameter of the function is a pointer, most of the time, this pointer parameter cannot be nullptr. If we cannot ensure that function users can call the function with the correct parameters each time, we need to check the function parameter validity at the "ENTRANCE" of the function, avoid larger errors caused by Invalid parameters and enhance program robustness. If you need to process invalid parameters, you can use conditional statements to prompt users based on the parameter validity or directly return the function execution failure information. For example:

// Set age bool SetAge (int nAge) {// check the validity of the parameter at the function entry // if the parameter is invalid, the user is prompted to reset if (nAge <0) {cout <"The Age cannot be negative. Set it again. "<Endl; // return false, indicating that the function fails to be executed. return false;} // If the parameter is valid, continue processing ...}

Here, we first use the if Condition Statement to check the parameter validity at the function entry. If the parameter is invalid, the system prompts the user to re-set the parameter and returns false, indicating that the function fails to be executed. If the parameter is valid, the system continues to process the function. By checking the parameter validity, the correctness of the function can be greatly improved, avoiding the occurrence of illogical errors such as negative age.

If you only need to check the validity of the parameter, and do not need to process the invalid parameter, you can also simply use assert to check the validity of the parameter, prevent functions from being called incorrectly. Assertion can accept a logical judgment Expression as a parameter. If the value of the entire expression is true, the assertion will not work, and the function continues to run down. If the expression value is false, the assertion will prompt that the assertion condition is not true. The parameter of the function is invalid and must be processed. For example, to design a division function Divide (), we can use assertion at the function entrance to avoid the occurrence of the error indicating that the divisor parameter is 0, the system also prompts whether the function is called incorrectly.

# Include <assert. h> // introduce the header file using namespace std; double Divide (int nDividend, int nDivisor) {// use assertion to determine whether the nDivisor parameter of the divisor is 0 // if it is not 0, "0! = NDivisor the expression value is true // assert (0! = NDivisor); return (double) nDividend/nDivisor;} int main () {// The Divide () function is incorrectly called double fRes = Divide (3, 0); return 0 ;}

If we call the Divide () function incorrectly with 0 as the divisor in the main function, when the function is executed to the assertion, the condition expression in the assertion is "0! = NDivisor "if it is set to false, the system will trigger the assertions. The system will terminate the program execution and prompt the location where the assertions occur, so that we can locate the errors and fix them. Until all the assertions are passed, the validity of the parameter is guaranteed.

It is worth noting that although the parameter legitimacy check at the function entrance can increase the robustness of the program to a certain extent, "there is no free lunch in the world ", it is at the cost of a certain procedural cost. Therefore, we need to balance the robustness and performance of the program to make the desired choice. If the program has higher robustness requirements, we should check the validity of parameters as much as possible. Otherwise, if the program has higher performance requirements, or the function will be executed multiple times, we will try to avoid checking the parameter validity at the function entrance. Instead, we will move the checking work forward to the function call and check the parameter validity before calling the function, it is expected that program performance will not be lost separately while ensuring program robustness.

Know More: static (compile-time) Assertions -- static_assert

In addition to checking the validity of parameters during runtime using assert assertions, we can also use static (compile-time) Assertions static_assert to check conditions in some compilation periods. A static assertion can accept a constant condition expression and a string as the parameter:

Static_assert (constant condition expression, string );

During the compilation period, the compiler evaluates the constant condition expression in the static assertion. If the expression value is false, that is, when the assertion fails, the Static Assertion outputs the string as an error message. For example:

Static_assert (sizeof (long)> = 8, "compilation requires 64-bit platform support ");

A static assertion is useful when determining whether a certain hypothesis is true (for example, whether the current platform is a 64-bit platform) and providing corresponding solutions, programmers can quickly locate the problem and fix it based on the prompts output by static assertions. It must be noted that static assertions are evaluated during compilation, so they cannot be used to rely on the hypothesis test of running time-varying values. For example:

Double Divide (int nDividend, int nDivisor) {// error: nDivisor is a runtime variable and cannot check static_assert (0! = NDivisor, "Division 0"); return (double) nDividend/nDivisor ;}

A condition expression in a static assertion must be a constant expression that can be evaluated during compilation. If we need to check certain conditions during runtime, we need to use runtime assert assertions.

2. process function return values with caution.

If the return value of a function is a pointer type, you cannot return a "Pointer" pointing to a local variable defined in the function body ". Because these local variables will be automatically destroyed at the end of the function execution, the memory location pointed to by these pointers becomes invalid memory, these pointers become "wild pointers" (the so-called "wild pointer" refers to a pointer pointing to an invalid memory area. At the beginning, this pointer may point to a variable or a memory resource requested. After the variable is destroyed or the memory resource is released, this region becomes an invalid memory area, and the pointer still pointing to this invalid memory area becomes a "wild pointer "). When we try to access the data pointed to by the function again after the function returns, its content may be unchanged or modified, it is no longer the data before the function returns, and these "Pointers" pointing to uncertain content will bring great security risks to the program. For example:

// Returns the pointer int * GetVal () {int nVal = 5; // returns the return & nVal ;}

Anything can happen when you get this pointer outside of the function and continue using it. For example:

// Get a pointer int * pVal = GetVal () returned from the function pointing to its local variable; // no one will predict the result of this action, maybe the earth will destroy * pVal = 0;

In addition to the above two rules on the function entry and return values, we should also observe the following four basic principles in the design and implementation of the function body:

1. The responsibilities of functions should be clear and uniform

Girls in love always like to hear each other say "I only love you, you are my only one ". Functions in C ++ share the same preferences as girls in love. In C ++, we always break down a major problem into multiple small problems, and functions are usually used to solve a small problem. This determines that its responsibilities should be clear and single.

Specifically, this function is used to solve a small problem. This is often reflected in the function name. We always use a verb or a verb to represent the function's responsibilities. For example, the print () function indicates that this function is responsible for printing the output, while the strcmp () function is used for string comparison. The function name should accurately reflect the responsibilities of a function. If we find that a function cannot be named by a simple verb or verb, this usually means that the function's responsibilities are not clear enough, maybe we still need to further refine it.

Singleton means that the entire function only performs the thing specified by the function name. The print () function only prints the output and does not compare strings. The strcmp () function only compares strings, instead of outputting strings. Once we find that a function does what it should not do, the best solution is to divide the function into two smaller functions to perform their respective duties without interfering with each other. For example, in a function that looks for the best scores, we have also learned how to find the worst scores. Although a function seems to have done two things on the surface, it achieves twice the result with half the effort. However, if we only need the best score, the worst score we can search for at the same time will consume performance for no reason, and the final result is often half the result. In this case, we should break down this function into two smaller functions, one dedicated to finding the best score, and the other dedicated to finding the worst score. In this way, the two functions perform their respective duties, so we can call a function independently. The two functions should not be mixed in the same function.

The clear and single function responsibilities are the most important rule in function definition. If we violate this rule, it would be tantamount to making a loud announcement that we are running on two ships. The end of this rule can be imagined.

2. The function code should be short and lean

The clear and simple function responsibilities determine that the function code should be short and refined. If you find that a function is too cumbersome and lengthy, consider whether its responsibilities are clear and uniform. Some people worry that short functions cannot implement powerful functions. In fact, after a good hierarchical design, functions call the next layer of functions, A complex function is divided into multiple small functions and implemented by the next-level function. Short functions can also implement very powerful functions. Some people worry that functions are short and lean, which will increase the number of functions and increase the amount of program code. The fact is that keeping functions short and lean not only reduces the amount of code in the program, but also reduces the amount of code. Because this process often extracts repeated code from the program into independent functions, avoiding the repetition of a lot of code, it will naturally reduce the amount of code. In addition, this process also makes our ideas clearer and increases the readability of the program code.

According to the general practical experience, a screen page should be able to fully display all the code of a function, so that we do not need to flip pages when viewing and editing this function, making it easier to read the code, it also reduces errors. If we cannot implement the functions of the entire function within the scope of this code, we should consider whether the responsibilities of this function are clear and simple, whether or not it can be further subdivided into multiple smaller functions. Keep in mind that no matter who you are, you also hate functions that are as smelly and long as a canvas.

3. The function should avoid too many nested Layers

In those soap bubbles, there is often a love triangle "I love you, you love him, and he loves me". If you do not pay attention to the function implementation, this triangular call relationship is also prone. In C ++ programs, we always use nested function calls to gradually break down a complicated problem, which can simplify the complicated problem and make it easy for us to break through. However, we should also pay attention to the levels of decomposition. If the principle of decomposition is unclear and the level of decomposition is too deep and too chaotic, it may be "I call you, you call him, he also called my "triangular love" Call relationship, and finally put the entire program into an infinite loop of nested calls. For example:

// Nested call of the function // declare int GetArea (); int GetWidth (); int GetHeight (); // nested call of the function forms an infinite loop int GetWidth () {return GetArea ()/GetHeight ();} int GetHeight () {return GetArea ()/GetWidth ();} int GetArea () {int w = GetWidth (); int h = GetHeight (); return w * h ;}

Here, the GetArea () function calls the GetWidth () function, while the GetWidth () function calls the GetArea () function in turn, forming a nested call loop, this loop continues until the final resource depletion program crashes. What is even more frustrating is that the compiler does not discover such logical errors, and therefore does not provide any prompt information, making such errors highly concealed and hard to discover.

If there are too many nested layers of functions, the entire program's structure will be confused, so that both the code writers and those who read the code will be confused and unable to extricate themselves. Every time we get lost, it's not a winding country path, but it's often a well-designed overpass. Because they are crossly And crossly, they can block our sight. Too many nested functions are equivalent to a cross-cutting overpass. Remember who called them before figuring out what they did. So, don't let our function nesting have too many layers, it will only confuse our program, and will not receive any good results.

4. The function should avoid repeated code.

If two functions have similar functions, a beginner's approach is to copy the Code already written in a function and paste it into another function. Code duplication is the root cause of programmer pain. This copy-and-paste process seems to save time for coding, but may actually hide various errors: the copied Code only Implements similar functions, we often need to modify it to meet new requirements. This process may introduce new chaos in the copied Code. On the other hand, if we find that there is an error in the copied code that needs to be updated, because the code is duplicated, We have to modify the same code in multiple places, sometimes you may even forget to modify the errors in the repeated code and leave the errors in the Code, resulting in more and more errors. It is precisely because, in this way, the time we spend to modify these possible errors often far exceeds the time saved by copying and pasting code, which is obviously not worth the candle.

So, if a function has similar functions, how can we avoid code duplication in the function? The simplest rule is: whenever we want to copy a large piece of code, think about whether we should extract the code into a separate function. In this way, both functions with similar functions can call the same function to implement similar functions. For example, when printing the PrintIn () function and the PrintOut () function, we need to print the company name in the header:

// Print the import ticket void PrintIn (int nCount) {// print the header cout <"ABC limited" <endl; cout <"Import ticket" <endl; // print the cout content <"Today's purchase" <nCount <"pieces" <endl;} // print the output ticket void PrintOut (int nCount, int nSale) {// print the header cout <"ABC Ltd" <endl; cout <"shipment list" <endl; // print the cout content <"Today's shipment" <nCount <"pieces" <endl; cout <"sales" <nSale <"Yuan" <endl ;}

By comparing the two pieces of code, we can find that in the two functions, the code responsible for printing the header is almost identical. In this case, we should extract this similar code into an independent function (PrintHeader ). This function performs the same functions in two sections of code (print the company name). For slightly different functions (print different single names), you can use parameters to differentiate them:

// The extracted PrintHeader () function void PrintHeader (string strType) that is responsible for printing the header {// print the header cout <"ABC Ltd" <endl; // use parameters to customize the behavior of the function to make the function more universal. cout <strType <endl;} // In PrintIn () and PrintOut () call the // PrintHeader () function in the function to print the header void PrintIn (int nCount) {// print the header PrintHeader ("Import ticket ");//...} Void PrintOut (int nCount, int nSale) {// print the header PrintHeader ("shipment List ");//...}

Using such function extraction not only avoids many problems caused by direct copy and paste, but also makes the code structure clearer and easier for coding. More importantly, the code after function extraction is easier to maintain later. If the company name changes in the future, or new content needs to be added to the header, we only need to modify a function of PrintHeader (), and The PrintIn () and PrintOut () functions can implement new functions without any modifications. This method saves more time and effort than directly copying and pasting.

The above four basic principles are summarized from practical experience. The Code of countless predecessors verifies the correctness of these rules. Simply put, they can be summarized into a pair of simple couplets: "We should be clear and single, so we should be short and lean; avoid too many nesting and avoid repeated code ". If you can put this couplet above the hall and read it all the time, you must be able to ensure Function Security and program prosperity.

 

Figure 5-10 "function down"

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.