Very practical code reading strategy!

Everyone likes code that is highly readable, and hopes that the code they take over is easy to read and understand, thereby reducing the workload of the handover, but not all code is easy to read. A very painful thing for developers.

There is a popular saying about code: code is read ten times as often as it is written, and the longer the product lives, the higher the ratio. With this in mind, we seem to be significantly underinvesting in "understanding code". Developers often place more emphasis on the ability to code than the ability to read and interpret existing code, even though this scenario occurs frequently in daily work.

The first 80-95% of development time should be spent reading code and documentation. In the process of studying existing code, you may learn a lot, and only after reading the code can you say: "This feature already exists, or adding this feature will do more harm than good".

This article will introduce you to some practical code reading strategies that you can use according to your actual situation.

1. Refactor local variables and methods

Sometimes a piece of code is so vague that it can mislead the reader or even make it difficult to reason about its meaning. A method with little risk is to rename local variables and private methods to more accurately describe what they do. These types of modifications will not affect functionality outside the current working file, and as long as care is taken to avoid naming conflicts, they will not cause logic errors. If possible, use your IDE's refactoring tools (rather than textual find and replace), so you can rename everything that's used in one click.

For example, consider the following JavaScript code

function ib(a, fn) {
return (a || []).reduce((o, i) => {
o[fn(i)] = i;
return o;
}, {});
}

It's very difficult to read, and the method name ib is useless for understanding what the function does. However, that doesn't prevent you from making inferences about it:

  • Since reduce is called on a (and it returns an empty array), a should be an array type.

  • The callback parameter i will be an element of this array.

  • The second parameter of reduce, an empty object {}, tells us that the callback parameter o is a dictionary (object).

So, by renaming, we can get the following result:

function ib(array, fn) {
return (array || []).reduce((dict, element) => {
dict[fn(element)] = element;
return dict; }, {});
}

Through the above adjustments, you can see that fn is the key to turn the array elements into a dictionary. This reveals the purpose of function ib: to convert an array into a dictionary, with a custom callback to determine the key to index each element. You can rename fn to getKey, and ib should be named indexBy. Renaming some identifiers helps us understand the code without changing its logic and thinking about all the parts at once. Modifications are highly recommended if possible. After all, this improves the readability of the code, which will benefit the entire team, while it does not increase or change the functionality of the program.

2. Figure out how the code is called

Most code will be called by other code. If you are struggling with a piece of code, it is very helpful to understand its function by figuring out how it is called. The method could be renamed to ThisBreaksOnPurpose. Then compile, although in the case of access through reflection, you will see errors at runtime, but the error prompt of compilation will tell where this method is used.

If the above method is not feasible, you can search the method name by text. If you're lucky, the name of this method is unique within the codebase. If it doesn't, you may end up with a larger result set and have to wade through a lot of unrelated code.

3. Search for similar codes

Sometimes, even when all the identifiers are well named and the use cases are clear, the code is hard to understand. Not all code conforms to coding idioms. Sometimes a particular operation does not follow coding conventions. In the worst case, the code in question appears in a working code base, while also not using obvious idioms.

Yet truly unique code is rare in long-lived codebases, especially on a single expression or line of code. If you spend a few minutes searching for similar code in your project, you may find some clues to solve the whole puzzle.

Full-text search is the easiest of these. You can select a salient code snippet to search for, and search tools often include a "whole word" search option, meaning a search for care.exe will not return results like scare.exertion. If you want to narrow things down even further, you can search with regular expressions instead of text phrases.

Of course, occasionally even regular expressions aren't enough to narrow things down, and no one wants to spend hours digging through search results for something that might not help. It's also worth learning some advanced search techniques. Many programmers prefer to use Unix's command-line tools like grep and awk, or on Windows, hand-written PowerShell scripts. My top choice is JS Powered Search, a VS Code extension that lets you define a logical search query in JavaScript.

4. Run unit tests

In a perfect code base, you can understand the state of the code by using unit tests. But most codebases are not perfect; unit testing efforts are often unnecessary for reasons of efficiency, and sometimes unit tests describe outdated behavior. Still, it's a good idea to review and perform code tests. At a minimum, they describe the input and output of the code.

If there are no unit tests or the unit tests are not comprehensive enough, you have a second chance to save. A test or two can be written to demonstrate whether there is a problem with the code. If you find a problem, fix it and then commit the change, it increases the stability of the code base and makes the code self-explanatory. You never have to worry about adding automated tests breaking existing functionality.

Tests take time to write, but doing so can greatly improve code execution efficiency. Tests are actual proof that the code works, and having unit tests in place gives you confidence that the functionality of the code is not broken.

5. Use the Debugger tool

Once you have unit tests, there are good mechanisms to help you debug step by step. Set a breakpoint or add a breakpoint/debugger statement at the top of this code. Then run the tests. Once a breakpoint is hit, execution pauses and you can advance one line at a time, step into and out of functions, and examine the values ​​of all variables in scope.

If you know which user actions triggered the relevant code, you can set breakpoints and run the program normally, interacting with the program's interface. If you do this, the feedback loop will be longer, but it will also use more realistic data, which may help you spot null references and edge cases.

Top-to-bottom line-by-line debugging may not be very useful for code that runs tens or hundreds of times, such as nested loops. For such code, you can add aggregated variables in each loop, so that you can see the total at the end of the loop. Many IDEs also allow you to set conditional breakpoints, which allow you to pause in a loop by setting a condition and enter the breakpoint to view the value of the corresponding variable.

6. Search knowledge base

If your team writes documentation as part of the development process, you can quickly skip this step. Documentation should not be the only source of truth, you should rely on code to understand how your program behaves.

While documentation can explain the "How" of code, it's often better at explaining the "Why". Sometimes you understand what a piece of code is doing, but from another angle something looks wrong. So you should make every effort to understand what information or constraints the original programmer was coding against before changing it.

A good internal document can also point you to teammates who know the truth. If you've gotten this far and done enough work, it's time to ask for help. Make sure to let the other person know what you're working on and what problem you're trying to solve, they'll most likely notice your blind spots.

7. Use version control comments

At this point, you've learned several effective code reading strategies. But even then, there might be unsolvable problems: a weird design decision, a way to break a codebase coding pattern, a code idiosyncrasy for no apparent reason.

A version control system can show the author and commit of any line of code in a code repository. In Git, it's the git blame command. Most systems call it "blue" or "annotate". You can run this command on the command line or in your IDE. What will appear is a line-by-line list of commits: a commit hash, a commit message, and an author.

If the last commit for that line of code doesn't make sense—for example, it was a formatting or whitespace change—you need to go through the file's change history to find the commit that introduced that line of code. Again, version control systems have tools to help you do this.

Once you have a PR and a ticket, you not only have the background of the code, but also the people involved with it: the author of the code, the PR reviewers, anyone who commented or updated the ticket, who signed the QA. If the previous methods don't work, it's time to chat with the seniors.

8. Understand first, then code

Through the study of the above steps, it may be helpful to you, especially the understanding of the code background and the realization of functions. Before you move forward, also consider refactoring the code to make it clear, creating new documentation, any time invested here will pay off for you and your team interacting with the code.

The ability to read code effectively is the secret weapon that can get you through technical interviews fast and make you a valued member of any team. Programmers who are good at writing code are valuable, and programmers who are good at reading code are even more valuable. When an error occurs in production or a new function needs to be developed urgently, the first and most important step is to understand that reading the code will allow you to reach the other shore smoothly.

Guess you like

Origin blog.csdn.net/Java_LingFeng/article/details/128682311