Conclusion How to Write Code to Avoid Pits as Much as Possible

Conclusion How to Write Code to Avoid Pits as Much as Possible #

When writing code, how can we avoid pitfalls as much as possible?

This course is coming to an end, and here I want to express my special thanks for your continued approval and support. For me, although I have spent almost all of my spare time in the past half year on creating this course and answering your questions, it has been tiring and difficult. However, seeing your serious learning and positive feedback on the course content, and witnessing your not only gaining knowledge but also igniting a passion for studying source code, I am also very happy and deeply feel that all the hard work has been rewarding.

I believe that along the way, you have not only understood the solutions to more than 130 common pitfalls in business code development, but also learned their underlying causes and how to use some common tools to analyze problems. This way, in the future, when encountering various pitfalls, you will have more methods and confidence to solve them.

How to Avoid Pitfalls? #

However, learning and analyzing these pitfalls is not our ultimate goal. The real goal is to learn how to avoid pitfalls when writing business code. Therefore, I would like to focus on discussing some methods to avoid pitfalls.

Pitfalls are often traps that we are not aware of. Although this course covers more than 130 potential errors in business development, I believe that there are thousands of pitfalls in the entire Java development field. Additionally, with the emergence of the Java language, various new frameworks, and technologies, we will constantly encounter various pitfalls. It is difficult to have a way to ensure that we will never encounter new problems.

What we can do is to avoid pitfalls as much as possible or minimize the impact of falling into a pit. With this in mind, I have 10 suggestions to share with you.

First, when encountering unfamiliar classes, do not use them arbitrarily without understanding them.

For example, I mentioned CopyOnWriteArrayList in the concurrency utility lecture. If you only think that CopyOnWriteArrayList is a thread-safe version of ArrayList and use it in a scenario with a large number of write operations without understanding its principles, you may encounter performance issues.

Over time, the JDK or various frameworks will continue to introduce various special classes, which are used for optimizing program performance in various nuanced scenarios. Before using these classes, we need to understand the origins of these classes and the problems they are designed to solve. Only when we confirm that they are suitable for our own scenarios can we use them.

Moreover, the more universal a utility class is, the easier it is to use; conversely, more advanced classes tend to be more complex and easier to fall into pitfalls. For example, StampedLock, a lock utility class mentioned in the “Locking Code” lecture, is much more complex in usage compared to ReentrantLock or synchronized, and it is easy to fall into pitfalls.

Second, try to use higher-level frameworks.

Generally, lower-level frameworks tend to provide more detailed configurations, allowing users to configure them according to their own needs, while less consideration is given to best practices. On the other hand, higher-level frameworks consider how to make it easier for developers to use them out of the box.

For example, in the lecture on HTTP requests, we talked about the concurrency limit of Apache HttpClient. If you use Spring Cloud Feign with HttpClient, you will not encounter the problem of a default limit of 2 concurrent connections per domain. This is because Spring Cloud Feign has already set this parameter to 50, which is sufficient for most scenarios.

Third, pay attention to security patches and version updates for various frameworks and components.

For example, the Tomcat server and serialization frameworks we use are the entry points for hackers. We need to keep track of stable major versions and patches for these components and frameworks, and update them in a timely manner to avoid the pitfalls of performance or security issues in the components and frameworks themselves.

Fourth, try to avoid reinventing the wheel and use popular frameworks.

The biggest advantage of popular frameworks is that they are mature. After being used and polished by a large number of users, almost all the problems you can think of have been encountered by others, and the frameworks have already come up with solutions. Many times, we may develop our own “lightweight” framework, but in fact, many complex frameworks started out as lightweight frameworks. However, these frameworks became more and more complex as they addressed various issues and made many considerations for extensibility. Complexity is not necessarily due to the inherent design of the framework.

If we develop our own framework, we may end up falling into pitfalls that others have already encountered. For example, if we directly use JDK NIO to develop network programs or frameworks, we may encounter the epoll selector spinning bug, which eventually leads to 100% CPU usage. On the other hand, Netty avoids these issues, so using Netty to develop NIO network programs is not only simpler but also helps avoid many pitfalls.

Fifth, when encountering errors during development, in addition to searching for solutions, it is more important to understand the principles.

For example, in the lecture on OOM, I mentioned the OOM problem caused by the configuration of a very large server.max-http-header-size parameter, which may come from solutions found online. The solutions provided by others on the internet may only be suitable for “themselves” and may not apply to everyone. Moreover, frameworks are iterated frequently, and a solution that is effective today may be ineffective tomorrow. The parameter configurations that are effective today may no longer be recommended or even become invalid in the new version.

Therefore, only by understanding the principles can we fundamentally prevent falling into pitfalls.

Sixth, there is a lot of information on the internet, but not all of it is reliable. The most reliable source is official documentation.

For example, when searching for information about Java 8, you may come across some materials that mention the higher efficiency of using the Files.lines method for file reading in Java 8. However, the demo code does not use try-with-resources to release resources. In the lecture on file I/O, I explained that not using try-with-resources will cause file handles to not be released. Actually, various online resources are shared experiences and insights for learning by everyone, which may not all be correct. Additionally, these resources provide Demos, demonstrating the functionality of a class in a specific aspect, but they may not consider issues such as resource release and concurrency comprehensively.

Therefore, when it comes to systematically learning a component or framework, I highly recommend referring to the official documentation of the JDK or third-party libraries. These documents generally do not contain erroneous examples and usually mention best practices and key points to pay attention to.

Seventh, perform unit testing and performance testing.

If you are developing a lower-level service or framework with a large audience and numerous branching processes, unit testing (or automated testing) is necessary.

Manual testing typically focuses on the main processes and changes, while only unit testing can ensure that any changes will not affect every detail of the existing service. Additionally, many pitfalls involve thread safety and resource usage, which only arise under high concurrency. Code that has not undergone performance testing can only be considered as having completed its functionality, but not yet guaranteeing robustness, scalability, and reliability.

Eighth, conduct design reviews and code inspections.

Everyone makes mistakes, and everyone has blind spots in their knowledge. Therefore, if the project’s design can be reviewed by an expert group in advance and if each section of code can be reviewed by at least three people, the likelihood of making mistakes can be greatly reduced.

For example, a developer familiar with IO would know that reading and writing files should be based on buffers. If they see code submitted by another colleague that reads and writes files byte by byte, they can identify performance issues in the code in advance.

Furthermore, some older resources still advocate using MD5 digests to store passwords. However, MD5 is no longer secure. If the project design has been reviewed by experienced architects and security experts within the company, security lapses can be avoided in advance.

Ninth, leverage tools to help us avoid pitfalls.

The fact is, when we make many trivial mistakes, it’s not that we don’t know, but rather due to carelessness. Even if we know that there may be 100 pitfalls, it would be difficult for us to check each line of code against these pitfalls one by one. However, if we can use tools to detect the explicitly defined pitfalls, we can avoid numerous trivial errors.

For example, pitfalls such as using “YYYY” for date formatting, using “==” for comparison, and the interplay between the original list and the sublist in List.subList can all be discovered by the Alibaba P3C code scanning plugin. I also recommend installing this plugin for your IDE.

Additionally, I suggest integrating the Sonarqube code static scanning platform into the CI process to perform comprehensive code quality scans on the code to be built and released.

Tenth, establish effective monitoring and alerting.

Problems like memory leaks, un-released file handles, and thread leaks are usually the result of an accumulation of quantitative change before reaching a qualitative change, ultimately leading to process crashes. If we can monitor various indicators of an application’s memory usage, file handle usage, IO usage, network bandwidth, TCP connections, thread counts, etc. from the beginning, and set up alerts based on reasonable thresholds, we may be able to detect and resolve problems in the early stages.

Also, when encountering alerts, we should not disregard them based on experience, assuming these problems are already known. We must remember that all alerts need to be addressed and documented.

These are the ten recommendations I want to share with you. By utilizing these recommendations well, we can greatly enhance our ability to detect pitfalls in Java development, avoid production failures caused by pressure, and minimize the impact of stumbling blocks.

Lastly, as they say, the teacher leads you to the gate, but the practice depends on the individual. I hope that during your continuing process of learning technology and writing code, you can cultivate the habit of researching principles, thinking, and summarizing problems, and gradually fill in the gaps in your knowledge network. By striving for perfection in code writing, producing robust code, and reducing online issues, not only will you have a better mood, but you will also receive more recognition and have more time for learning and improvement. In this way, our personal growth will be faster and form a positive cycle.

Additionally, if you have time, I would like to ask you to fill out a course survey and provide feedback on your thoughts and suggestions regarding this course. Although today marks the end of the course, I will continue to pay attention to your comments and hope that you will continue to learn the course content and interact with me through the comments section.

You can also continue to share this course with friends and colleagues around you so that we can continue to exchange ideas and discuss the mistakes that can be made when writing Java business code.