C ++ Engineering Practice (3): use code formats that are conducive to Version Management

Source: Internet
Author: User

Chen Shuo (giantchen_at_gmail)

Blog.csdn.net/solstice

Version controllingProgramThe basic skills of C ++ programmers are no exception. One of the basic functions of version management is tracking.CodeChanges give you a clear idea of how the code is turned into the current one step by step, and what internal changes are made each time check-in is performed. Whether it is a traditional centralized version management tool, such as subversion, or a new distributed management tool, such as git/HG, comparing the differences between the two versions (revision) is its basic function, this is commonly known as "Diff ".

The diff output is a peephole, and its context is limited (diff-u displays three rows before and after by default ). When doing code review, if you can find that there is a problem with the code changes, it would be even better.

Both C and C ++ are free-format languages, and linefeeds in code are treated as white space. (Of course, we are talking about preprocess ). For the compiler, the same code can be written in multiple ways, such

Foo (1, 2, 3, 4 );

And

Foo (1,

2,

3,

4 );

The lexical analysis results are the same, and the semantics is the same.

For people, the two statements are read differently. For version management tools, modifications to the same function often lead to different diff. The so-called "conducive to version management" refers to the rational use of line breaks in the Code, the diff tool is friendly, so that the diff results clearly and clearly express the changes to the Code. (Diff is generally in the unit of behavior or word. This article only considers the most common diff by lines .)

Here are some examples.

Diff-friendly code format 1. multiline comments are also used //, no need /**/

The C ++ style is recommended for article 2 of Objective C ++ written by Scott Meyers. I would like to add a reason for this: it is friendly to diff. For example, I want to comment out a large piece of code (this is not a good practice, but it is sometimes encountered in practice). If/**/is used, the resulting diff is:

Diff -- git a/examples/ASIO/tutorial/timer5/Timer. cc B/examples/ASIO/tutorial/timer5/Timer. CC --- A/examples/ASIO/tutorial/timer5/Timer. CC ++ B/examples/ASIO/tutorial/timer5/Timer. CC@-18,6 + 18,7 @ Class printer: boost: noncopyable loop2 _-> runafter (1, boost: BIND (& printer: print2, this ));}+ /*~ Printer () {STD: cout <"final count is" <count _ <"\ n";-38,6 + 39,7 @ Class printer: boost :: noncopyable loop1 _-> quit ();}}+ */Void print2 (){

Can we see from this diff output What code is commented out?

If // is used, the results will be much clearer:

 Diff -- git a/examples/ASIO/tutorial/timer5/Timer. cc B/examples/ASIO/tutorial/timer5/Timer. CC --- A/examples/ASIO/tutorial/timer5/Timer. CC ++ B/examples/ASIO/tutorial/timer5/Timer. CC @-18,26 + 18,26 @ Class printer: boost: noncopyable loop2 _-> runafter (1, boost: BIND (& printer: print2, this ));} -~ Printer ()-{-STD: cout <"final count is" <count _ <"\ n ";-}  + //~ Printer () + // {+ // STD: cout <"final count is" <count _ <"\ n"; + //}  -Void print1 ()-{-muduo: mutexlockguard lock (mutex _);-If (count _ <10)-{-STD: cout <"timer 1: "<count _ <" \ n ";-+ Count _; -- loop1 _-> runafter (1, boost: BIND (& printer: print1, this);-}-else-{-loop1 _-> quit ();-}-} + // Void print1 () + // {+ // muduo: mutexlockguard lock (mutex _); + // If (count _ <10) + // {+ // STD: cout <"timer 1:" <count _ <"\ n"; + // + Count _; + // loop1 _-> runafter (1, boost: BIND (& printer: print1, this )); + //} + // else + // {+ // loop1 _-> quit (); + //} Void print2 (){

In the same way, when the comment is canceled, // is clearer.

In addition, if you use/**/to annotate multiple rows, you may not be able to see whether you are modifying the code or modifying the comment from diff. For example, the following diff seems to have modified the call parameters of muduo: eventloop: runafter:

Diff -- git a/examples/ASIO/tutorial/timer5/Timer. cc B/examples/ASIO/tutorial/timer5/Timer. CC --- A/examples/ASIO/tutorial/timer5/Timer. CC ++ B/examples/ASIO/tutorial/timer5/Timer. CC@-32, 7 + 32, 7 @ Class printer: boost: noncopyable STD: cout <"timer 1:" <count _ <"\ n "; + + Count _;-Loop1 _-> runafter (1, boost: BIND (& printer: print1, this ));+ Loop1 _-> runafter (2, boost: BIND (& printer: print1, this ));} Else {

In fact, this modification occurs in the comments (you need to add the context to see it, diff-u 20, multiple procedures, reducing work efficiency), without affecting code behavior:

 Diff -- git a/examples/ASIO/tutorial/timer5/Timer. cc B/examples/ASIO/tutorial/timer5/Timer. CC --- A/examples/ASIO/tutorial/timer5/Timer. CC ++ B/examples/ASIO/tutorial/timer5/Timer. CC @-20, 31 + 20, 31 @ Class printer: boost: noncopyable/* ~ Printer () {STD: cout <"final count is" <count _ <"\ n";} void print1 () {muduo :: mutexlockguard lock (mutex _); If (count _ <10) {STD: cout <"timer 1:" <count _ <"\ n "; + + Count _;-loop1 _-> runafter (1, boost: BIND (& printer: print1, this); + loop1 _-> runafter (2, boost:: BIND (& printer: print1, this);} else {loop1 _-> quit ();}} */ Void print2 () {muduo: mutexlockguard lock (mutex _); If (count _ <10) {STD: cout <"Timer 2: "<count _ <" \ n "; ++ count _;

In short, do not annotate multiple lines of code.

This may be a time-and-time migration. Everyone is using // to comment out this suggestion. The third edition of Objective C ++ removes this suggestion.

2. Definitions of local variables and member variables

The basic principle is that a line of code defines only one variable, such

Double X;

Double Y;

When a Double Z is added to the Code in the future, the diff output will show at a glance what is changed:

 
@-63,6 + 63,7 @ PRIVATE: int count _; Double X; Double Y;+ Double Z;}; Int main ()

If you write x and y in a row, the output of diff can only be viewed.

 
@-61,7 + 61,7 @ PRIVATE: muduo: Net: eventloop * loop1 _; muduo: Net: eventloop * loop2 _; int count _;-Double X, Y;+ Double X, Y, Z;}; Int main ()

Therefore, defining only one variable in a row is more conducive to version management. The same principle applies to Enum member definitions, array initialization lists, and so on.

3. Parameters in function declaration

If the number of parameters of a function is greater than three, a new line is followed by a comma, so that each parameter occupies one line, facilitating diff. Take muduo: Net: tcpclient as an example:

Class tcpclient: boost: noncopyable {public:Tcpclient (eventloop * loop, const inetaddress & serveraddr, const string & name );

If a parameter is added or modified by the tcpclient constructor in the future, it is easy to see it from diff. This is probably more efficient than counting commas in a long line of code.

4. Parameters for function calling

When calling a function, if there are more than three parameters, write the real parameter branch. Take muduo: Net: epollpoller as an example:

 
Timestamp epollpoller: poll (INT timeoutms, channellist * activechannels) {int numevents =: Epoll_wait (epollfd _, & * Events _. Begin (), static_cast <int> (events _. Size (), timeoutms );Timestamp now (timestamp: Now ());

In this way, if a new parameter is introduced for future refactoring (well, epoll_wait won't have this problem ), the diff of the Function Definition and function call has the same form (for example, a line of content is added in the last and second rows), so it is easy to check whether there is any dislocation with the naked eye. If the parameter is written in one line, you have to open your eyes with commas.

5. Write the class initialization list

In the same way, the class initialization list (initializer list) follows the principle of one row. In this way, if a new member variable is added in the future, there will be two parts (class definition and ctor definition) diff has the same form, so that errors are invisible. Take muduo: Net: buffer as an example:

 
Class Buffer: Public muduo: copyable {public: static const size_t kcheapprepend = 8; static const size_t kinitialsize = 1024; buffer (): Buffer _ (kcheapprepend + kinitialsize), readerindex _ (kcheapprepend), writerindex _ (kcheapprepend){} // Omitted
 
PRIVATE:STD: vector <char> buffer _; size_t readerindex _; size_t writerindex _;Static const char kcrlf [];};

Note: The initialization list must be in the same order as the data member declaration.

6. indentation related to namespace

Google's c ++ programming specification clearly states that namespace does not add indentation. This makes sense to facilitate diff-P to display the function name on each diff chunk.

If diff is performed on the function implementation, the chunk name is the function name, which can be seen at a glance. For example, red dashes.

If diff is performed on the class, the chunk name is the class name.

Diff was originally designed for the C language. There is no namespace indentation in the C language, so it will find the "top write" function by default as the name of a diff chunk, if there is a space before the function name, it does not recognize it. Muduo Code follows this rule, for example:

 
Namespace muduo {///// Time stamp in UTC, in microseconds resolution. //// this class is immutable. /// it's recommended to pass it by value, since it's passed in register on x64. // class timestamp: Public muduo: copyable, public boost :: less_than_comparable <timestamp> {// Class is written from the first column without indentation
// Function implementation is also written from the first column without indentation.Timestamp timestamp: Now () {struct timeval TV; gettimeofday (& TV, null); int64_t seconds = TV. TV _sec; return timestamp (seconds * kmicrosecondspersecond + TV. TV _usec );}

On the contrary, the code of some libraries in boost is Indented by namespace. In this case, diff often does not know which class member function is modified.

This may be solved by setting the regular expression of the diff function name, but if we write code, we should note that the function is "written to the top level ", you do not need to change the default diff settings. In addition, regular expressions cannot fully match the function name. Because the function name is context-free syntax, you cannot write a regular syntax to match the context-independent syntax. I can always write some function declaration to invalidate your regular expression (think about the return type of the function, it may be a very complicated thing, let alone a parameter ). What's more, the C ++ syntax is context-related. For example, do you guess Foo <bar> qux; is it an expression or a variable definition?

7. Public and private

I think this is a defect in the C ++ syntax. If I move a member function from the public area to the private area, I cannot see what I did from diff, for example:

Diff -- git a/muduo/NET/tcpclient. h B/muduo/NET/tcpclient. h --- A/muduo/NET/tcpclient. h ++ B/muduo/NET/tcpclient. h@-37,7 + 37,6 @ Class tcpclient: boost: noncopyable void connect (); void disconnect ();-Bool retry () const;Void enableretry () {retry _ = true;} // set connection callback. @-60, 6 + 59,7 @ Class tcpclient: boost: noncopyable void newconnection (INT sockfd); // NOT thread safe, but in loop void removeconnection (const tcpconnectionptr & conn );+ Bool retry () const;Eventloop * loop _; Boost: scoped_ptr <connector> connector _; // avoid revealing Connector

Can I see from the above diff that I have changed retry () to private? I don't have a good solution for this. I can't write public: or private: in front of every function?

Java and C # are both better at this. They place public/private modifiers in the definition of each member function. This increases Information Redundancy and makes diff results more intuitive.

8. Sequence of header files

Except the header files that must be placed in the first place, the rest are arranged alphabetically. This minimizes the possibility of conflict when multiple users modify the file. In addition, the file list in makefile is also alphabetically arranged to reduce the possibility of conflict.

Refer to muduo/**/*. CC

Please add.

Grep-friendly code style Operator Overloading

C ++ tools are scarce. In a project, it may not be too difficult to find a function definition (at most, it is to analyze the overload and template specialization ), however, it is much harder to find a function. Unlike Java, CTRL + Shift + G in eclipse can find all reference points.

If I want to perform a refactoring, I want to find all the places in the code that use muduo: timedifference and determine whether the work is feasible. Basically, the only way is grep. Using grep cannot exclude functions with the same name and content in comments. This also explains why to use // to guide the annotation, because in grep, we can see that this line of code is in the annotation at a glance.

In my opinion, Operator Overloading should be used only in combination with STL algorithm/container, such as transform () and Map <t, u>. It is recommended to use the name function in other cases. One of the reasons is that I cannot find where I used operator-() with grep -(). This is also the reason that muduo: Timestamp only provides operator <() but not operator + () operator-(). I provide two functions: timedifference and addtime to implement the required functions.

For example, the callback of Google protocol buffers is class closure, and its interface uses virtual function run () instead of virtual operator ()().

Static_cast and C-style cast

One of the reasons why C ++ needs to introduce transformation operators such as static_cast is that expressions such as (int *) pbuffer basically cannot be determined by grep as forced type conversion, you cannot write a regular expression that exactly matches the type conversion. (Again, the syntax is context-independent and cannot be done using regular expressions .)

If * _ cast is used for type conversion, I can know where reinterpret_cast is used in the Code as long as grep is used, so that I can quickly check whether there are any errors. To emphasize this, muduo has enabled the compilation option-Wold-style-cast to help find C-style cast. This will help us find problems during compilation.

All for efficiency

If you use a graphical file comparison tool, it seems that you can avoid the problems listed above. However, neither the Web Client nor the inline diff nor diff by lines can solve all the problems, and the efficiency is not necessarily higher.

For (2), if you want to know who added the Double Z, you can use git blame or SVN blame to immediately find the initiator when writing the code at a branch. If you write a line, You have to compare the revisions of the file one by one manually, because this line of Double X = 0.0, y = 1.0, Z =-1.0; may have been modified many times, you have to read it one by one to know when the variable Z is added. This blame case also applies to 3, 4, and 5.

For example, (6) If you have modified a line of code, you still need to perform scroll up to find the function to be changed. If you are looking at it, there is a possibility of "looking at your eyes", and you have to look at it again. This is a waste of time. Using better graphical tools cannot reduce waste. On the contrary, I think it is a waste.

In another common work scenario, I came to the office in the morning and updated the code. Then I glanced at diff output to see which files were moved and which Code was changed yesterday, this is one or two commands that can solve the battle in a few seconds. If you use a graphical tool, you need to open the diff link or click a new tab to view the side-by-side comparison of the file. (If you do not do this, you cannot see enough context, similar to diff output), click the mouse to scroll the page to see what others have changed. To be honest, I think this is not as efficient as diff.

(To be continued)

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.