preface

As a board member of the Golang branch of the company’s code committee, I review a lot of code and read a lot of reviews. I found that many students need to improve their code review and writing good codes. Here, I would like to share some of my ideas and ideas.

Why do technical staff including the leader have to do code review

As The saying goes, ‘Talk Is Cheap, Show Me The Code’. Knowing is easy but doing is difficult. It’s easy to remember what others have said, organize the language, and say it again. Practice what you know. You may have heard it through the grapevine and think you know it, but can you do it? Have the ability to think about and improve your current practices and code details in practice? To put it mildly, many people simply know and subscribe to a design concept, leading to a false sense of reassurance that their skills are not bad. What difference does it make whether he understands these principles/concepts as a result or not? It becomes self-deception.

Code, is the design concept landing place, is the presentation and fundamental technology. In the review process, students can communicate with each other on the ground, instead of discussing air-to-air. They can think about practical problems and learn from each other. We can master the best practices accumulated in the team! Of course, if the leader has no time to write the code, he just reviews the code and points out that some practices of other students are not good, he should give suggestions on good practices. Even if he does not write the code himself, he should think a lot about the best practices.

Why do students think and summarize best practices in review

I’ll start with a summary of my own: An architect is someone who understands a wide range of design concepts and principles, practices in various languages and associated toolchains (ecosystems), vertical industry model understanding, custom system model design and engineering practice specifications. Then control the development convenience, maintainability, testability and operation quality of 300 + 000 lines of code projects.

Strong technical people, mainly can be divided into the following directions:

  • A lot of techniques, a lot of ideas for discovering techniques, a lot of programming contests, for example, are compared to this. But this doesn’t seem to be very useful for engineering.

  • Field founders such as John Carmack, who developed a methodology for efficient rendering of modern computer graphics. Whether or not someone else would have invented it without him, he was the first. In 1999, Carmack made time magazine’s list of the 50 most influential people in technology at Number 10. However, there are few similar palace positions, not enough for us to share, no business of ours.

  • In the 1980s, Dr. Kai-fu Lee insisted on using the framework of the implicit Markov model and successfully developed the world’s first large vocabulary continuous speech recognition system Sphinx. Few of our engineers seem to be good at this.

  • Product success xiao Longge is the benchmark.

  • Best practices This is something anyone can do, as defined by the architect above. If you go well on this road, you can build a technical team and build high-quality systems for any company.

As can be seen from the above discussion, the path of evolution for us ordinary engineers is to hone best practice methodology and implement details.

The root cause of bad code

Before we talk about what is good code, let’s talk about what is bad. Computers are artificial, and we create our own problems and think about solutions.

Duplicate code

// BatchGetQQTinyWithAdmin get tinyID of QQ uin, need tiny and login state of main Uin // friendUins can be empty list, Tiny func BatchGetQQTinyWithAdmin(CTX context.Context, adminUin uint64, friendUin []uint64) ( adminTiny uint64, sig []byte, frdTiny map[uint64]uint64, err error) { var friendAccountList []*basedef.AccountInfo for _, v := range friendUin { friendAccountList = append(friendAccountList, &basedef.AccountInfo{ AccountType: proto.String(def.StrQQU), Userid: proto.String(fmt.Sprint(v)), }) } req := &cmd0xb91.ReqBody{ Appid: proto.Uint32(model.DocAppID), CheckMethod: proto.String(CheckQQ), AdminAccount: &basedef.AccountInfo{ AccountType: proto.String(def.StrQQU), Userid: proto.String(fmt.Sprint(adminUin)), }, FriendAccountList: friendAccountList, }Copy the code

Because the protocol was poorly designed in the beginning, the first person to use the interface, without code like this one, implemented a fill in request structure with embedded logic code, which was fine at first. But when there’s a second person, a third person doing something similar, we’re not going to be able to reconstruct this protocol, we’re going to have to be cumbersome forward compatible. And each student should understand how to fill in the protocol above, understand that there is a problem, trigger the bug. Or, if a misconception is prevalent, we have to find all of these repeated pieces and revise them.

When you want to read a data, find two places, do not know which to choose. When you want to implement a function and find two RPC interfaces, two functions can do it, you don’t know which one to choose. Have you ever faced such a ‘life problem’? It doesn’t matter which way you choose, the code you wrote has taken a solid step on the road to shit.

But A little copying is better than A little dependency. Here is a mouth, not open.

Here, I must make an additional remark. People use TRPC. Feel encouraged to ‘do one Git per service’. That, you this service access db code, RPC code, all kinds of reusable code, is the use of everyone reuse git code? Git git git git git git git git git git git git git git This generic Git interface should not know which code under Git permanently abandons forward incompatible changes because of their own forward incompatible changes?

Decisions that worked early on no longer work

Most of the time, we write the first version of the code, is not too big a problem. For example, the following code

Func (s *FilePrivilegeStore) Update(key def.PrivilegeKey, clear, isMerge bool, subtract []*access.AccessInfo, increment []*access.AccessInfo, policy *uint32, adv *access.AdvPolicy, shareKey string, ImportQQGroupID uint64) Error {// Obtain the previous data info, err := s.set (key) if err! = nil { return err } incOnlyModify := update(info, &key, clear, subtract, increment, policy, adv, shareKey, importQQGroupID) stat := statAndUpdateAccessInfo(info) if ! incOnlyModify { if stat.groupNumber > model.FilePrivilegeGroupMax { return errors.Errorf(errors.PrivilegeGroupLimit, "group num %d larger than limit %d", stat.groupNumber, model.FilePrivilegeGroupMax) } } if ! isMerge { if key.DomainID == uint64(access.SPECIAL_FOLDER_DOMAIN_ID) && len(info.AccessInfos) > model.FilePrivilegeMaxFolderNum { return errors.Errorf(errors.PrivilegeFolderLimit, "folder owner num %d larger than limit %d", len(info.AccessInfos), model.FilePrivilegeMaxFolderNum) } if len(info.AccessInfos) > model.FilePrivilegeMaxNum { return errors.Errorf(errors.PrivilegeUserLimit, "file owner num %d larger than limit %d", len(info.AccessInfos), model.FilePrivilegeMaxNum) } } pbDataSt := infoToData(info, &key) var updateBuf []byte if updateBuf, err = proto.Marshal(pbDataSt); err ! = nil { return errors.Wrapf(err, errors.MarshalPBError, "FilePrivilegeStore.Update Marshal data error, key[%v]", key) } if err = s.setCKV(generateKey(&key), updateBuf); err ! = nil { return errors.Wrapf(err, errors.Code(err), "FilePrivilegeStore.Update setCKV error, key[%v]", key) } return nil }Copy the code

Now, the code is fine, no more than 80 lines long, and the logic is clear. But when YOU do isMerge where you judge the logic, if you add more logic, if you push the local number of lines above 50, this function, it’s going to taste bad. Two problems arise:

1) The code in the function is not at the same logical level. When I read the code, I suddenly fell into the details of the 50 lines of isMerge logic processing. Before I finished reading the code, I forgot what the previous code said, and needed to go back and forth to challenge the size of my brain.

2) If there is a problem with the code, should students who add new code change or not change the code written by others? Who will carry the bug? This is a soul search.

Premature optimization

You’ve heard a lot about this, so I won’t repeat it here.

There are no demands on rationality

‘Either way you can write it,’ or ‘IT’s okay with me,’ are phrases I often hear.

// Get IP func (I *IPGetter) Get(cardName String) string {i.l.lock () IP, found := i.m[cardName] i.l.RUnlock() if found { return ip } i.l.Lock() var err error ip, Err = getNetIP(cardName) if err == nil {i.m[cardName] = IP} i.L.nunlock () return IP} i.L.nunlock () can be placed in the current position, You can also put it under I.L.Rock () and make it defer. Both seemed to work when they were first constructed. At this time, the attitude of many students becomes not resolute. In fact, this must be deferred. i.l.Lock() defer i.l.Unlock() var err error ip, err = getNetIP(cardName) if err ! = nil {return "127.0.0.1"} i.m[cardName] = IP return IPCopy the code

Such modification is very likely to happen, but it will still become defer. Then, why not just defer at the beginning and enter the most reasonable state? Not at the beginning into the most reasonable state, in the follow-up collaboration, other students are likely to make mistakes!

Always object oriented/always love encapsulation

I majored in software engineering. The first programming language I learned was c++. The textbook is this one. When I finished reading the teaching material, the first into the door of the program design, for the inside of the ‘encapsulation’, surprised for heaven, how wonderful design ah, object-oriented, how intelligent design ah. However, over the years, I have seen danniu ‘Yunfeng’ sneer at ‘graduates using mysql API like to make class encapsulation and reuse’; See all sorts of confusing class definitions; I realized that I had to read the whole inheritance tree to confirm a small logical branch. I learned many times that I had to work hard to suppress my resistance to scrutinizing a smart-aleck wrapped code and identifying my bugs. With the exception of UI class scenarios, I think less inheritance and more composition.

template<class _PKG_TYPE> class CSuperAction : public CSuperActionBase { public: typedef _PKG_TYPE pkg_type; typedef CSuperAction<pkg_type> this_type; . }Copy the code

This is the code for SSPP. CSuperAction and CSuperActionBase, now super, now base, super and SuperBase are in what two levels of abstraction, no one can read through the code, no one can understand. I want to confirm any details, I have to read through multiple levels of code, where is the encapsulation?

The author didn’t get the class name right. So, the question is, can you do it? Can a new T1.2 student design class name and class tree well? Even for simple business models, it takes countless ‘bad’ object abstraction practices to produce a student with qualified class abstraction skills. Isn’t this destructive to large but loose team collaboration? You already have a set of inheritance trees, and you can only add functionality to the tree. The old inheritance tree is no longer suitable for the new requirements. You change all the classes in the tree and where they are used. No, a normal person would give up and start making shit.

Encapsulation is I don’t care about implementation. But for a stable system, every layer of design can go wrong. The ABI, there’s always appropriate usage and inappropriate usage, it’s real and we don’t care how the encapsulated part is implemented, right? No, you can’t. Bugs and performance problems often occur when you use a wrapped function in the wrong way. Even android, ios API, Golang, Java ready-made API, we often have to explore the implementation, in order to use the API well.

Wouldn’t it make more sense to start with a transparent function? Users want to know the details, come in, my implementation is easy to read, you will understand, use not lost! For functions with complex logic, we should also emphasize the reappearance of the internal working mode of the function, which enables readers to imagine and present the complete process in their brain, so that users can easily understand and be sure of it and do not get lost when using it.

No design at all

This is the scariest thing, all demand, getting started is a wank, ‘What is design? I have a file 5W lines, a function 5K lines, can’t do all the requirements? ‘From the very first line of code, there was no design, walking randomly through mud puddles, no sense of what anyone else was looking at, dancing alone, producing code that fulfilled the requirements and destroyed the people who took over. I’m not going to give you an example, but everyone should be able to find this code in their project class.

You have to think metaphysically

Often, students like to listen to the details of “work” when they listen to lectures and open classes. That’s no problem. But how many years of work have you learned? Have you built your own technical thinking ‘surface’, entered the three-dimensional ‘engineering thinking’, and connected the technical details with the requirements that the system needs to meet in thinking? When listening to a requirement, can you think about how your code package should be organized and how your functions should be organized?

So, how do technology points connect to requirements? The answer is simple. You need to sum it up over time, with some clear principles, thought processes. Thinking about how to summarize is particularly like thinking about philosophical problems. From some trivial details, from specific situations to some principles, axioms. At the same time, when we accept the principle, we should not accept and remember the principle itself, but should be the structural principle, let the principle in their own reasoning, fully grasp the scope of its application.

To be more specific, the metaphysical thinking process for engineering best practices is:

The problems encountered in engineering practice are classified from two perspectives: problem type and method type, and some limited applicable principles are summed up from point to surface. The combination of these principles into your own project code is a combination of many aspects to build a three-dimensional best practice solution. When your solution can be adapted to projects with 30w+ lines of code and more than 30 people, you’re an architect! When you have a project that is multi-endpoint, multi-language, over 300W lines of code, involving over 300 people, the code quality is still high, the code is still iterating on itself efficiently, eliminating outdated code every day, filling in high-quality replacements and new code.

Congratulations, you are already a very senior architect! Further, you have a unique or comprehensive understanding of a business model, build an industry-first solution, and combine that with the ability to achieve a high-quality implementation. There’s nothing to talk about. You’re already an expert engineer. Higher level, I don’t understand, not here to discuss.

So, we’re going to start thinking and summarizing all over again? No, there is a book called “The Art of Unix Programming” that I have read three times at different times. In a moment, I will talk about some of the principles mentioned in it that I feel are especially worth mentioning at Tencent. These principles can be used to determine the quality of code in code review. But before I do that, I have to talk about another very important topic, model design.

The model design

Anyone who designs third party authorized logins without reading oAUTH2.0 RFC will end up inventing another lame Oauth.

When I graduated in 2012, I chatted with a south China Tech graduate who had gone to Guangzhou Unicom. At that time, he said that he was very unhappy at work, because he did not often write code in his work, and he thought that he had the algorithm proficiency of ACM competition gold level + the familiarity with CPP code, wrote down one pointer after another to operate memory, and could not write any program, and could not do anything well. And I thought, that makes sense, what can I do with my programming tools?

Now, I tell him, Linux, Chromium engine, Windows Office, you can’t do it. The reason was that he was not in the engineering world of software engineering. You can’t build the HZMB by moving bricks. However, this is not a good answer, the evidence is too distant from us. See the small, see the small. I would answer now, you can’t do it, it’s as simple as a access system, do you know how to do it? Stack up a bunch of logical hierarchies one-dimensional if else? Simple as a shared file management, do you know how to do it? Pile up a bunch of logical hierarchies one-dimensional ife LSE? You unicom has tens of thousands of servers, you want how to write a management platform? Pile up a bunch of logical hierarchies one-dimensional ife LSE?

Can you fulfill the three seemingly simple requirements mentioned above? Think about how many years amazon, Ali Cloud tosses, finally found the container +Kubernetes killer. Here, it takes Google years of practice in BORG system to put forward an excellent service choreography domain model. In the domain of permissions, there are MODELS such as RBAC, DAC, MAC, etc., and when it comes to business, there will be different details. As Domain Driven Design says, without good Domain thinking and model abstraction, the logical complexity is n^2 exponential, how many ifelse do you have to write, how many possible if paths do you have to think about to cover all the unexpected cases. You must be able to Domain think and explore, model disassemble/abstract/build.

I’ve been asked, how do I get this ability effectively? I failed to answer this question, which is like asking me, how do I get an MIT PhD? I can’t answer that. The only answer is, to enter a certain field, is to first look at the thinking of predecessors, stand on the shoulders of predecessors, and then use their general ability to further think. As for how to build a good general thinking ability, you may need to go to the ivy league to read a book 🙂 or, in engineering practice to think and exercise their ability!

At the same time, model-based code can better adapt to the changing needs of product managers. For example, a Calendar app, think simple, not too simple! The key ‘userid_date’ is used to record a user’s daily schedule. I’m just going to go one step further and I’m going to design a task that’s going to be distributed to 100 million people, and I’m going to create a task that’s going to add a record to 100 million people, right? You have to change the design, change the DB. One step further, to pull out all the transactions that a user and a person want to participate in together, join all the tasks of both people. It seems ok. What about all the missions with 100 people? 100 people mission to join? That’s not realistic.

Ok, you introduced a group ID, so, your original ‘userid_date’ as the key design, will have to change and do data migration again? Often comes a demand, you have to overturn the system to start from scratch, or can only refuse the user’s demand, such a fighting power, still bashfully call yourself an engineer? You should start by thinking about your business domain, thinking about the possible model boundaries of your calendar application, bringing in all the capabilities that you might want to do, building a model, designing a set of common store layer interfaces, logic code based on common interfaces. When the product is evolving, it’s about adding content to the model, not reinventing it.

Thinking about model boundaries and building model details are two very important abilities, which most Tencent product managers do not have. You should have them, which will be extremely beneficial to the whole team. When you look at product managers, listen to the requirements points that they’ve come up with out of responsibility for the user experience, and come to yourself with a complete model that covers those bits and pieces.

Model design is an aspect of metaphysical thinking, a particularly important aspect. Next, we will copy the practice of Unix operating system construction for our proposed predecessors’ practical experience and ‘axioms’ summary. In my coding/code review, I think on the shoulders of giants. The discovery of classical mechanics was not repeated, but an advance into relativity.

UNIX Design Philosophy

People who don’t understand Unix are doomed to end up reinventing a shoddy Unix. – Henry Spenncer, 1987.11

The following quote is so classic that I have to paraphrase it (from UNIX Programming Arts) : “Each branch of engineering and design has its own technical culture. In most engineering fields, unwritten industry literacy is as important as standard manuals and textbooks in terms of the composition of a professional’s literacy (and as professional experience accumulates over time, it often becomes more important than the books). Senior engineers accumulate a lot of tacit knowledge as they work, and they pass it on to their juniors in a Zen-like way. Software engineering is an exception to this rule: technology is changing so fast, the software environment is changing so fast, and software technology culture is emerging temporarily.

However, there are exceptions to the rule. Few software technologies have proved durable enough to evolve into strong technical cultures, distinctive art and design philosophies. “

Now, let me use my understanding to explain a few principles that we often fail to follow.

Keep It Simple Stuped!

You’ve probably heard the KISS principle before. But are you really following it? What is Simple? Simple? Rob Pike, one of the main designers of Golang, said ‘simplicity to simplicity’. Is simplicity the same as simplicity?

First of all, simplicity is not a problem. The solution to our first impression is simplicity. I say, “Feel it.” To do something easy, to do it in the simplest and most effective way, is a difficult thing.” For example, to do a tripartite authorization, OAUTH2.0 is simple, all concepts and details are compact, complete, and easy to use.

Did you find it easy to design oAuth2.0 effects? To be simple, we need to have a comprehensive understanding of the problem we are dealing with, and then we need to keep accumulating and thinking, so as to understand the problem from all angles and levels and polish a popular, compact and complete design, just like the interaction design of ios. Simplicity is not easy to achieve, we need to accumulate thinking in the process of constant time and code review, trigger thinking in PK, and summarize thinking in communication, so as to do better and get closer to “simplicity”.

Two classic model diagrams, simple and comprehensive, feel, do not understand, can immediately Google to learn: RBAC:



logging:



Principle 3 Combination principle: consider splicing and combination in design

I’ve talked about OOP, I’ve talked about inheritance. So how do we organize our modules? Yes, in a combination of ways. How is the Linux operating system built so close to home? In a small sense, we have a series of a business request data set, if the use of BaseSession, XXXSession inherit BaseSession design, in fact, this inheritance tree, it is difficult to adapt to the endless changes. But if you use composition, you can disassemble UserSignature and other components as needed and combine them as needed, constantly adding new components without the mental burden of remembering the old inheritance tree.

Using a combination is all about making sure you know exactly what parts you have. If there are too many parts, in fact, there will be a high mental burden to complete the process of assembling the final product, each part unfolds, dazzling. For example, QT is a generic UI framework. If you look at its Class list, there are more than 1000 of them. If you don’t have an inheritance tree to organize it, to spread it out, to compose a page, it becomes too mentally taxing to bear. OOP effectively controls complexity in extremely complex scenarios where numerous elements need to be present at the same time. ‘And what is the price, Gul ‘dan? ‘The trade-off is that when you do this top-down design from the beginning, it becomes very difficult to adjust every time.

In the actual project, students of different professional levels work together to modify the code of a server, and it will appear that students of lower ranks can not correct any of the modifications and are unable to make modifications, while students of higher ranks can correct the modifications but are unwilling to make large-scale modifications, and the whole project becomes more unreasonable. Students who do not fully understand the entire inheritance tree are not qualified to make any modifications to the inheritance tree, and collaboration becomes impossible. Code changes are the result of relying on a senior architect to intensely monitor changes in the inheritance system, while lower-level students have their hands tied. Combination is a good solution to this problem, and the problem is continuously subdivided, so that each student can well conquer the points they need to conquer and achieve a package. The product logic code only needs to combine various packages to achieve the effect.

This is the Golang standard library’s definition of an HTTP request, which is the result of all the features of an HTTP request. The general/variant/multi-implementation parts are abstracted through Duck Interface, such as Body IO.ReadCloser. If you want to know what the details are, start with the parts that make up the Request. To change, just change the parts. [After this code, compare.NET HTTP to OOP based abstractions]

// A Request represents an HTTP request received by a server
// or to be sent by a client.
//
// The field semantics differ slightly between client and server
// usage. In addition to the notes on the fields below, see the
// documentation for Request.Write and RoundTripper.
type Request struct {
  // Method specifies the HTTP method (GET, POST, PUT, etc.).
  // For client requests, an empty string means GET.
  //
  // Go's HTTP client does not support sending a request with
  // the CONNECT method. See the documentation on Transport for
  // details.
  Method string

  // URL specifies either the URI being requested (for server
  // requests) or the URL to access (for client requests).
  //
  // For server requests, the URL is parsed from the URI
  // supplied on the Request-Line as stored in RequestURI. For
  // most requests, fields other than Path and RawQuery will be
  // empty. (See RFC 7230, Section 5.3)
  //
  // For client requests, the URL's Host specifies the server to
  // connect to, while the Request's Host field optionally
  // specifies the Host header value to send in the HTTP
  // request.
  URL *url.URL

  // The protocol version for incoming server requests.
  //
  // For client requests, these fields are ignored. The HTTP
  // client code always uses either HTTP/1.1 or HTTP/2.
  // See the docs on Transport for details.
  Proto string // "HTTP/1.0"
  ProtoMajor int    // 1
  ProtoMinor int    // 0

  // Header contains the request header fields either received
  // by the server or to be sent by the client.
  //
  // If a server received a request with header lines,
  //
  // Host: example.com
  // accept-encoding: gzip, deflate
  // Accept-Language: en-us
  // fOO: Bar
  // foo: two
  //
  // then
  //
  // Header = map[string][]string{
  // "Accept-Encoding": {"gzip, deflate"},
  // "Accept-Language": {"en-us"},
  // "Foo": {"Bar", "two"},
  // }
  //
  // For incoming requests, the Host header is promoted to the
  // Request.Host field and removed from the Header map.
  //
  // HTTP defines that header names are case-insensitive. The
  // request parser implements this by using CanonicalHeaderKey,
  // making the first character and any characters following a
  // hyphen uppercase and the rest lowercase.
  //
  // For client requests, certain headers such as Content-Length
  // and Connection are automatically written when needed and
  // values in Header may be ignored. See the documentation
  // for the Request.Write method.
  Header Header

  // Body is the request's body.
  //
  // For client requests, a nil body means the request has no
  // body, such as a GET request. The HTTP Client's Transport
  // is responsible for calling the Close method.
  //
  // For server requests, the Request Body is always non-nil
  // but will return EOF immediately when no body is present.
  // The Server will close the request body. The ServeHTTP
  // Handler does not need to.
  Body io.ReadCloser

  // GetBody defines an optional func to return a new copy of
  // Body. It is used for client requests when a redirect requires
  // reading the body more than once. Use of GetBody still
  // requires setting Body.
  //
  // For server requests, it is unused.
  GetBody func() (io.ReadCloser, error)

  // ContentLength records the length of the associated content.
  // The value -1 indicates that the length is unknown.
  // Values >= 0 indicate that the given number of bytes may
  // be read from Body.
  //
  // For client requests, a value of 0 with a non-nil Body is
  // also treated as unknown.
  ContentLength int64

  // TransferEncoding lists the transfer encodings from outermost to
  // innermost. An empty list denotes the "identity" encoding.
  // TransferEncoding can usually be ignored; chunked encoding is
  // automatically added and removed as necessary when sending and
  // receiving requests.
  TransferEncoding []string

  // Close indicates whether to close the connection after
  // replying to this request (for servers) or after sending this
  // request and reading its response (for clients).
  //
  // For server requests, the HTTP server handles this automatically
  // and this field is not needed by Handlers.
  //
  // For client requests, setting this field prevents re-use of
  // TCP connections between requests to the same hosts, as if
  // Transport.DisableKeepAlives were set.
  Close bool

  // For server requests, Host specifies the host on which the
  // URL is sought. For HTTP/1 (per RFC 7230, section 5.4), this
  // is either the value of the "Host" header or the host name
  // given in the URL itself. For HTTP/2, it is the value of the
  // ":authority" pseudo-header field.
  // It may be of the form "host:port". For international domain
  // names, Host may be in Punycode or Unicode form. Use
  // golang.org/x/net/idna to convert it to either format if
  // needed.
  // To prevent DNS rebinding attacks, server Handlers should
  // validate that the Host header has a value for which the
  // Handler considers itself authoritative. The included
  // ServeMux supports patterns registered to particular host
  // names and thus protects its registered Handlers.
  //
  // For client requests, Host optionally overrides the Host
  // header to send. If empty, the Request.Write method uses
  // the value of URL.Host. Host may contain an international
  // domain name.
  Host string

  // Form contains the parsed form data, including both the URL
  // field's query parameters and the PATCH, POST, or PUT form data.
  // This field is only available after ParseForm is called.
  // The HTTP client ignores Form and uses Body instead.
  Form url.Values

  // PostForm contains the parsed form data from PATCH, POST
  // or PUT body parameters.
  //
  // This field is only available after ParseForm is called.
  // The HTTP client ignores PostForm and uses Body instead.
  PostForm url.Values

  // MultipartForm is the parsed multipart form, including file uploads.
  // This field is only available after ParseMultipartForm is called.
  // The HTTP client ignores MultipartForm and uses Body instead.
  MultipartForm *multipart.Form

  // Trailer specifies additional headers that are sent after the request
  // body.
  //
  // For server requests, the Trailer map initially contains only the
  // trailer keys, with nil values. (The client declares which trailers it
  // will later send.) While the handler is reading from Body, it must
  // not reference Trailer. After reading from Body returns EOF, Trailer
  // can be read again and will contain non-nil values, if they were sent
  // by the client.
  //
  // For client requests, Trailer must be initialized to a map containing
  // the trailer keys to later send. The values may be nil or their final
  // values. The ContentLength must be 0 or -1, to send a chunked request.
  // After the HTTP request is sent the map values can be updated while
  // the request body is read. Once the body returns EOF, the caller must
  // not mutate Trailer.
  //
  // Few HTTP clients, servers, or proxies support HTTP trailers.
  Trailer Header

  // RemoteAddr allows HTTP servers and other software to record
  // the network address that sent the request, usually for
  // logging. This field is not filled in by ReadRequest and
  // has no defined format. The HTTP server in this package
  // sets RemoteAddr to an "IP:port" address before invoking a
  // handler.
  // This field is ignored by the HTTP client.
  RemoteAddr string

  // RequestURI is the unmodified request-target of the
  // Request-Line (RFC 7230, Section 3.1.1) as sent by the client
  // to a server. Usually the URL field should be used instead.
  // It is an error to set this field in an HTTP client request.
  RequestURI string

  // TLS allows HTTP servers and other software to record
  // information about the TLS connection on which the request
  // was received. This field is not filled in by ReadRequest.
  // The HTTP server in this package sets the field for
  // TLS-enabled connections before invoking a handler;
  // otherwise it leaves the field nil.
  // This field is ignored by the HTTP client.
  TLS *tls.ConnectionState

  // Cancel is an optional channel whose closure indicates that the client
  // request should be regarded as canceled. Not all implementations of
  // RoundTripper may support Cancel.
  //
  // For server requests, this field is not applicable.
  //
  // Deprecated: Set the Request's context with NewRequestWithContext
  // instead. If a Request's Cancel field and context are both
  // set, it is undefined whether Cancel is respected.
  Cancel <-chan struct{}

  // Response is the redirect response which caused this request
  // to be created. This field is only populated during client
  // redirects.
  Response *Response

  // ctx is either the client or server context. It should only
  // be modified via copying the whole Request using WithContext.
  // It is unexported to prevent people from using Context wrong
  // and mutating the contexts held by callers of the same request.
  ctx context.Context
}
Copy the code

Looking at the abstraction of Web services in.NET, just looking at the end, without looking at the full picture of the entire inheritance tree, I have no way of knowing where a particular detail I care about lies. Furthermore, I could not change any functionality in the entire HTTP service without understanding and familiarity with the overall design, and it was very easy to break the overall design without being aware of it.

Speaking of composition, there is a closely related term called plug-in. Everyone is having fun with vscode. How is it more successful than visual studio? If VS Code achieves the capabilities of Visual Studio by adding a bunch of plug-ins, it will become something similar to Visual Studio, called VS Studio. As you can see, most of the time we don’t need most of the features of Visual Studio, and we want the flexibility to customize some of the more niche capabilities and use some niche plug-ins. Even more, we want to choose plug-ins of the same type with different implementations. This is the power of combinations, all kinds of combinations, it is simple, yet meet all kinds of needs, flexible, to implement a plug-in, do not need to master a large system. The same goes for code. At least in the back-end development world, composition smells a lot better than OOP.

Rule 6: The mean rule: Don’t write a big program unless you have no other way to do it

Some students may feel that the program to write a large number of good hands to review T11, T12. As soon as leaders see the evaluation plan, they are easy to think: it is very big, very good and very comprehensive. But do we really need to write such a big program?

And I said, “Well, Gul ‘dan, at what cost?” . The trade-off is that the more code you have, the harder it is to maintain and adjust. Ken Thompson, the father of C, said, “I get a greater sense of accomplishment from removing a line of code than from adding one.” Let’s be stingy with our code. If you can make the system small, don’t make it big. Tencent no lack of 200W + line of client, very large, very cattle. However, the students asked themselves, is it still necessary to adjust the structure? Hand Q students, look at their own code, ever sigh? Do small things that can be done in small size, seek generalization, isolate modules and capabilities through Duck Interface (even multi-process, multi-thread for isolating capabilities), always think about cutting the amount of code, in order to maintain the maintainability of the code and adjust its vitality in the face of future requirements and architecture. Client-side code, UI rendering module can be complex to the sky, non-UI parts should pursue the simplest, ability to interface, replaceable, recombination ability.

When we review our code, we should pay most attention to the definition of core struct, build a complete model, core interface, clarify the external dependence of abstract model, and clarify the external capabilities provided by abstract model. The rest of the code is to implement the details inside the model in the simplest, most mundane code.

Principle 7 Transparency principle: Design should be visible for review and debugging

First of all, let’s define what transparency and explicitness are.

“Software systems are transparent if there are no dark corners and hidden depths. Transparency is a passive quality. If you can actually predict all or most of a program’s behavior and create simple mental models, the program is transparent because you can see what the machine is really doing.

A software system is demonstrable if its functions are designed to help people build correct mental models of what and how the software does. So, for example, good documentation helps improve visibility for users; For programmers, good variable and function names help improve explicitability. Dominance is an active quality. To do that in software, it’s not enough to be obscure, you have to try to be helpful.”

If we want to write good programs and reduce bugs, we need to strengthen our control over the code. You should always be able to understand how the function/reuse code you are calling is roughly implemented. Otherwise, you might call IO blocking functions in a single-threaded state machine server and let your server throughput drop to the bottom. Furthermore, in order for everyone to have control over their own code, the functions that everyone writes must have a high degree of transparency. Instead of writing some function/code that you can’t understand for a while, the person who is forced to use your code will simply give up control, or even give up reusing your code, and start all over again, heading towards the abyss of ‘making duplicate code’.

Transparency is actually relatively easy to achieve, people consciously exercise for a month or two, can do very well. But dominance is not easy. One phenomenon is that you write no more than 80 lines of each function, and I can understand every line, but you call layers of functions, many function calls, combined to achieve a certain function, read twice, still can’t understand. The third time might get the gist. Probably, but it’s so complicated that it’s hard to build in your head the whole process of how you do it. As a result, the reader has no control over your code at all.

The criterion for manifestability is very simple, and if you look at a piece of code, if you understand it, you will understand it. But how do you do it well? That is to pursue reasonable grouping of functions, reasonable upper and lower levels of functions, the same level of code will appear in the same function, the pursuit of easy-to-understand function grouping and stratification, is the road to explicit.

Of course, complex such as Linux operating system, Office documents, the problem itself is very complex, disassembling, stratification, combination no matter how reasonable, it is difficult to establish a mental model. At this point, complete documentation is required. Complete documentation also needs to be present closest to the code, ‘knowing that there is documentation for the complex logic’, rather than actual documentation that the reader doesn’t know about. Take a look at HTTP.Request in the Golang library above, and see how hard it is to be explicit? Yeah, just learn it.

Rule 10: Rule of populism: Avoid novelty in interface design

Design programs that are too unconventional may make it harder for others to understand.

In general, we define a ‘point’ by using x as the x-coordinate and y as the y-coordinate:

type Point struct {
 X float64
 Y float64
}
Copy the code

You just have to be different and precise:

type Point struct {
 VerticalOrdinate   float64
 HorizontalOrdinate float64
}
Copy the code

Good. You’re very precise, and the average person can’t argue with you. But most people don’t read your VerticalOrdinate as quickly, easily and conveniently as they read X. You are deliberately creating collaboration costs.

The above example is common, but it is not the least innovation principle to illustrate the most. ‘result := 1+2 ‘–> ‘result = []int{1, 2}’ –> ‘result =3 ‘–> ‘result = []int{1, 2}’ I can’t imagine it. “The other side of the principle of least variance is to avoid the appearance of wanting to die when the reality is slightly different. This can be extremely dangerous because similarity often leads to false assumptions. So it’s better to have things that are clearly different rather than looking almost the same.” – Henry Spencer.

When you implement db.add () and do db.addorUpdate (), someone uses your interface and mistakenly overwrites the data.

Principle 11 silence: If a program has nothing to say, say nothing

This principle should be one of the most frequently broken principles. A short code inserts various’ logs (” CMD XXX Enter “), ‘log(“req data “+ req.string ())’, and is very afraid of not printing enough information. The fear of not knowing if the program is successful always ends with ‘log(” success “). But, let me ask you, do you really have the patience to read a bunch of logs of other people’s code? If you don’t need one, just print another one in a pile of logs with a special mark ‘log(” this_is_my_log_ “+ XXXXX)”? As a result, the first author printed a log that was of no value when passing code to others or collaborating with others, and made it more difficult to read.

As soon as a service runs, it frantically hits the log, and also hits a bunch of logs when the request processing is normal. Logs roll in, drowning in error logs. Error logging is no longer effective, and simply tail through the log, dazzled, and unable to see any problems, is’ trying to catch problems’ and ‘not catching problems at all’?

Silence is golden. In addition to simple stat logs, if your program ‘speaks’, the information it throws must be valid! Printing a log(‘ process fail ‘) is also worthless. What fails? Which user with which parameters failed in which link? If you speak up, give all the information you need. Otherwise it is silent, indicating that they are working well. No voice is the best news, now my work is normal!

“Well-designed programs treat the user’s attention as a limited and valuable resource that needs to be used only when necessary.” Programmer own main force, also be precious resource! Only when necessary does the log remind the programmer, ‘I have a problem, take a look,’ and it must give enough information to explain what is happening. Not that programmers need a lot of tools to figure out what’s going on.

Whenever I publish a program, I spot check a machine and look at its logs. I was happy to find that there were only logs of external access per minute, number of internal RPCS/latency distribution. I know, this minute, it’s 100% success rate again, no problem!

Rule 12 Remedy rule: When an exception occurs, exit immediately with sufficient error information

The problem is really simple, if there’s an exception, the exception doesn’t go away because we try to hide it. Therefore, procedural errors and logical errors should be treated strictly separately. It’s a matter of attitude.

Exceptions are the norm for Internet servers. Logical errors are analyzed through metrics statistics. For bugs, we must strictly collect the necessary information at the earliest location of the problem, and loudly tell developers and maintainers, ‘I have an exception, please fix me immediately! ‘. It could be panic that was not captured. You can also make the recover mechanism at the top level, but you must obtain accurate information about the exact location of the exception during recover. You can’t have an intermediate catch mechanism where you lose a lot of information and pass it up.

Many Java developers don’t distinguish between procedural errors and logical errors, and are either tolerant or strict, which can be devastating to the maintainability of code. There are no bugs in my program. If there were, I would have fixed them then.” Only in this way can the quality of the program code be relatively stable, and extinguishing the fire when it appears is the best way to extinguish the fire. Of course, a more effective way is to fully automated test prevention 🙂

Specific practice points

There are a lot of questions about the direction of thinking. Big questions of principle and direction. Let me just give you a few more details. After all, you have to get started, you start with execution, and then you summarize thinking, and you can copy my way of thinking. The following is for the Golang language, other languages are slightly different. And, I cannot at the moment think of all the rules I carry out, which is why I stress the importance of ‘principles’, which are enumerable.

  • For code format specification, 100% strict implementation, serious tolerance can not a bit of sand.

  • The document should never exceed 800 lines. If it does, think about how to open the document. Engineering thinking is accumulated when opening documents.

  • Function pairs should never exceed 80 lines, and beyond that, you have to think about how to split functions, think about grouping functions, think about levels. Engineering thinking is accumulated when opening documents.

  • Code can be nested at no more than four levels, and then you have to change. Think about early return. Engineering thinking is accumulated when opening documents.

    if ! needContinue { doA() return } else { doB() return } if ! needContinue { doA() return }

    doB() return

The following is an early return, which logically decouples both ends of the code.

From directory, package, file, struct, function layer by layer, information must not be redundant. A definition like file.fileProperty. Only when each ‘attribute’ appears in only one position can it provide the possibility of ‘doing a good logical and defining grouping/stratification’.

Multiple levels of directories are used to organize the information hosted by your code, even if some of the intermediate directories have only one subdirectory.

As code expands, older code that violates some design principles should be refactored immediately in place to keep code quality up. For example: open documents; Disassembling functions; Use a Session to store all information about a complex process function. Readjust the directory structure.

With this in mind, we should try to keep the project code organized and hierarchical. My current practice is to keep everything in git except for special generic code. Especially common, less modified code, gradually independent of Git, as a child Git connected to the current project Git, let goland Refactor feature, various Refactor tools to help us fast and safe local refactoring.

Your project code should have an inherent hierarchy and logical relationship. Flat tiling is very bad for code reuse. How to reuse, how to organize reuse, is bound to become a ‘life problem’. T4-t7 students are incapable of solving this problem.

If the code being reviewed is short but you don’t understand it at first glance, there’s something wrong. If you can’t see it, look for high-level students to communicate. This is a time when you and other students who don’t review code grow up.

Type less journals. To log, bring key index information with you. The necessary log must be typed.

When in doubt, ask without being afraid of being wrong. Let the code author explain. Don’t be afraid to ask minimal questions.

Don’t say ‘suggestions’, ask questions, is just, you pk but I, have to change!

Please actively use TRPC. Always stand with the boss! The consensus with your boss about code quality is the best way to build code quality on your team.

Eliminate repetition! Eliminate repetition! Eliminate repetition!

The main development

Finally, let me say one more word about ‘trunk development’. The reason is simple: Only with less than 500 lines of code reviewed each time, reviewer will be able to review it quickly and with little chance of missing something. With more than 500 lines, reviewer will not be able to see them in detail, but only browse. And it’s much easier to tweak logic in 500 lines of code than it is to tweak logic in 3,000 or more lines of code, reducing not just six times, but one or two orders of magnitude. There is a problem, which is adjusted as soon as it shows up, that does not impose a large modification burden on the person who is revew.

There are many good materials and books about CI(Continuous Integration). You should learn them in time.

Unix Programming Arts

I suggest you find this book and read it. In particular, T7 and higher. You have accumulated a lot of code practice and need to think about ‘engineering’. A lot of engineering methodologies are out of date, and this book is the exception among the exceptions. The content it expresses has not become obsolete as software technology continues to change.

Zen Buddhism says’ do not stand the text ‘(do not stand the text, teach other, direct to the people, see the nature Buddha), a lot of truth and feeling can not be conveyed by words, the expression ability of words, can not express. People often feel reassured because they have heard and know something, and think “I know this”, but they can’t do it in practice. Knowing is easy, but doing is difficult. In engineering practice, it is no different from ‘not understanding this truth’.

Once, I interviewed a director at another company, and he talked like a package, and the code was pulled out, and he didn’t do it at all, just hearsay. His exploration of engineering practice was almost over. I can only wish you can do a good job of upward management, take their own pure management road. Please do not say their pursuit of technology, is a technical person!

Therefore, you are not only looking at my article, but in practice to continue to practice and accumulate their own ‘teaching other biography’.

Source: Tencent Technology Engineering