How can you ensure your algorithms are readable and understandable by others?
Algorithms are the core of many computational tasks, but they can also be complex, abstract, and hard to communicate. Whether you are working on a team project, sharing your code with others, or teaching algorithms to students, you need to make sure your algorithms are readable and understandable by others. In this article, you will learn some tips and best practices for writing clear, concise, and consistent algorithms that can be easily followed and modified.
One of the first steps to ensure your algorithms are readable and understandable by others is to choose a suitable notation. Notation is the way you represent your algorithms using symbols, variables, operators, and expressions. There are different types of notation, such as pseudocode, flowcharts, or formal languages, and each has its own advantages and disadvantages. You should choose a notation that matches your purpose, audience, and level of detail. For example, if you want to convey the general logic of your algorithm without worrying about syntax, pseudocode might be a good option. If you want to show the sequence and branching of your algorithm visually, flowcharts might be more effective. If you want to specify the exact implementation of your algorithm in a programming language, formal languages might be necessary.
-
In my experience, actual implementation with pseudocode parts instead of complex logic worked the best. If actual implementation of the core of the algorithm can be written in the time you spent writing pseudocode, go for actual implementation. If algorithm requires some complex logic, you can replace it with free function which may not contain logic at all. This can be considered as pseudocode. With this approach you'll have almost ready algorithm with implementation details hidden (and possibly even not yet implemented).
-
One useful approach I've discovered is to begin by creating a high-level visual representation of the algorithm. This involves describing the input, the desired outputs, and the flow of data through the main algorithm components. It's best to keep most of the intricate details out of this initial representation. You can move on to developing a more detailed embodiment of the algorithm using pseudocode, providing a deeper level of detail. Based on my experience, repeating this process at each iteration of algorithm design proves very effective. In essence, it guides the reader from an initial, perhaps less efficient solution to a more advanced version of the algorithm, all while highlighting the improvements made and the reasons behind them.
Another important step to ensure your algorithms are readable and understandable by others is to follow naming conventions. Naming conventions are the rules and guidelines for naming your variables, functions, parameters, and constants. They help you avoid confusion, ambiguity, and errors in your algorithms. You should follow naming conventions that are consistent, meaningful, and descriptive. For example, you should use camelCase or snake_case to separate words in your names, avoid using single letters or numbers as names, and use prefixes or suffixes to indicate the type or scope of your names. You should also avoid using names that are reserved keywords, have special meanings, or are similar to other names.
-
You missed names that are easily misspelled should not be used either. I have had experience with Hungarian Notation, and I have to say that it can be quite complex when taken to an extreme, but a lightweight version of that can work. Sometimes you have operations that are opposite of each other, so their names should follow normally expected guidelines for things such as Off/On, Create/Destroy Open/Close, Initialize/Shutdown for examples. You don't want to see "Initialize/Close or Open/Destroy for example. Semantics matter.
-
You may also consider using kebab-case instead of snake_case in naming of files. In en keyboard layout it will not require shift button to be pressed to name files and kebab-case is more readable. I also recommend avoiding using p_ notation for pointers in C++. Since auto keyword introduced in C++11 and smart pointers became more popular, having p_ in variable names does not make much sense. The code without p_ looks much better and majority of IDEs change '.' to '->' while you typing if it is needed.
-
At times, the initial version of the algorithm's pseudocode might feature less-than-ideal variable names or inelegant constructions. However, don't let that stifle your creativity. Instead, focus on refining data structures and optimizing execution times after a few iterations. Once you've made these improvements, you can then turn your attention to adhering to coding conventions. In my view, fixating on names and conventions too early in the process can detract from the enjoyment of algorithm design.
A third step to ensure your algorithms are readable and understandable by others is to add comments and documentation. Comments and documentation are the text that you add to your algorithms to explain, clarify, or justify your code. They help you communicate your intentions, assumptions, and design choices to others. You should add comments and documentation that are relevant, concise, and accurate. For example, you should use comments to explain the purpose and logic of your code, document the inputs and outputs of your functions, and highlight any limitations or edge cases of your algorithms. You should also update your comments and documentation whenever you change your code.
-
It depends on the organization whether you need to add comments or not. In my experience, code that you write should be self-explanatory. If you need to add comments that explain what this code does, there is a chance the code is not clear enough. Though it is not related to the comments which are used for automatically generated docs. In this case, especially if you have infrastructure that generate those docs, you need to add comments.
-
Although some people say the code is enough documentation, I disagree. I have run into too many situations where the developers implemented things in ways you wanted to understand what was going on in their heads when they wrote them. Comments always help with that understanding, as well as make the code more readable and learnable from. New Hires on a project will appreciate that very much. Assumptions are usually encapsulated in code Asserts() and sometimes comments. But there will always be people who don't want the comments much, while others will go about documenting everything. This can be very helpful for modern development IDEs since they give most of popup hint info on classes/functions, and those doc hints come from comments
A fourth step to ensure your algorithms are readable and understandable by others is to use indentation and spacing. Indentation and spacing are the way you arrange your code using tabs, spaces, and line breaks. They help you create a clear and consistent structure for your algorithms. You should use indentation and spacing that are uniform, logical, and aligned. For example, you should use indentation to show the hierarchy and nesting of your code blocks, use spaces to separate operators and operands, and use line breaks to separate statements and sections. You should also avoid using too much or too little indentation and spacing that can make your code hard to read.
-
Early on I learned to use Insert Spaces, with 4 spaces for each indent. It makes things very clear and not too smashed together to avoid getting confused easily. When I started working on Web software with JavaScript/TypeScript this all changed to my teams using Insert Spaces with 2 spaces for each indentation. I believe their justification was that it saved on the size of the files(minor) and was easier for them to type (a little lame). Using Tabs can be a nightmare, since your cursor can end up doing all kinds of unexpected things while you edit with tabs. Another thing to note is that the width of the code lines and how they wrap is important. We are no longer limited to small terminals with 80 char line limits.
A fifth step to ensure your algorithms are readable and understandable by others is to simplify and refactor your code. Simplifying and refactoring your code are the processes of improving your code quality, performance, and maintainability by making changes that do not alter its functionality. They help you eliminate unnecessary, redundant, or complex code that can make your algorithms difficult to understand and modify. You should simplify and refactor your code that are modular, efficient, and elegant. For example, you should use functions to avoid repeating code, use loops and recursion to reduce the number of steps, and use operators and expressions to simplify your calculations. You should also test your code after simplifying and refactoring to ensure it works as expected.
-
You need to have automatic tests that cover majority of the scenarios of the algorithm before refactoring. Scenarios covered by automatic tests should include edge cases and special cases that you know current implementation of the algorithm supports. Sure, happy path scenarios should also be included in the automatic tests, but during refactoring happy path scenarios are not the ones that usually affected. Edge cases and special cases quite often got lost during refactoring as software engineers tend to test the code on happy path scenarios only. It also worth mentioning that handling of edge/special cases cannot be lost as highly likely it was added as a fix of customer reported issues.
A sixth step to ensure your algorithms are readable and understandable by others is to review and test your code. Reviewing and testing your code are the ways of checking your code for errors, bugs, or flaws that can affect its correctness, reliability, and security. They help you verify and validate your algorithms and ensure they meet the requirements and specifications. You should review and test your code that are thorough, systematic, and objective. For example, you should use code reviews to get feedback from others, use debugging tools to identify and fix errors, and use testing techniques to measure and improve your code quality. You should also document and report your review and testing results and make any necessary changes to your code.
-
As a computer science professor, I found that teaching algorithms directly from textbooks wasn't the best approach. Instead, I developed a method where I first introduced students to simpler, easy-to-grasp versions of algorithms before moving on to the more complex, optimized ones. This step-by-step approach made algorithms more accessible, even if it took more time. For example, I would start with a basic version of an algorithm and later show how it could be improved. For instance, I would start by explaining a basic version of an algorithm like Prim's algorithm and later illustrate how it could be enhanced using a priority queue. I always incorporated this step while empirically testing each improvement and providing analytical proofs.