📜 ⬆️ ⬇️

5 simple rules for easy to read code

Does your own or someone else's code slip away?

Can't understand the algorithm?

Spend a lot of time debugging, but you can’t find the wrong initialization, but do you want to enjoy coding?

Remember the rules below and apply them!

The article does not cover the basic rules for naming variables and functions, syntactic indents, or large-scale refactoring. We consider 5 simple rules to simplify the code and reduce the load on the brain in the development process.

Consider the process of perception of data in order to relate the described rules with the process of perception and determine the criteria for simple code.

The simplified process of perception consists of the following steps:

  1. Receiving through receptors data correlate with previous experience.
  2. If there is no correlation, it is noise. Noise is quickly forgotten. If there is something to correlate with, recognition of facts takes place.
  3. If the fact is important - we memorize, or we generalize, or we act, for example, we speak or type the code.
  4. A generalization is used to reduce the amount of memorized and analyzed information.
  5. After summarizing, the information is again correlated and analyzed (step 1).


Scientists divide memory into short-term and long-term. Short-term memory is small in volume, but retrieving and saving information performs instantly. Short-term memory - brain cache. It can store 7 + -2 words, numbers, items. Long-term memory is more in volume, but it requires a lot of energy (effort) to store and retrieve information than short-term.

Findings:


We proceed to the description of the rules.

Rule 1. We use the statement in conditions, we get rid of "not".

Removing the "no" operator reduces the number of elements analyzed. Also, in many programming languages, the “!” Operator is used for the negation operator. This sign is easy to miss when reading the code.

Compare:

if (!entity.IsImportAvaible) { //код 1 } else { //код 2 } 

After:

 if (entity.IsImportAvaible) { //код 2 } else { //код 1 } 

Rule 2. Reduce the level of nesting.

While analyzing the code, each level of nesting is required to be kept in memory. Reduce the number of levels - reduce the costs of fuel.

Consider ways to reduce the number of nesting levels.

1) Return management. We cut off some of the cases and focus on the remaining ones.

 if (conributor != null) { //код } 

Convert to

 if(contributor == null) { return; } //код 

or

 while(условие 1) { if(условие 2) { break; } //код } 

or

 for(;условие 1;) { if(условие 2) { continue; } //код } 

An advanced example is given in rule 5. Note the discard exception.

2) the selection method. The name of the function is the result of the generalization.

 if(условие рассылки) { foreach(var sender in senders) { sender.Send(message); } } 

at

 if(условие рассылки) { SendAll(senders, message); } void SendAll(IEnumerable<Sender> senders, string message) { foreach(var sender in senders) { sender.Send(message); } } 

3) Combining conditions.

 if (contributor == null) { if (accessMngr == null) { //код } } 

at

 if (contributor == null && accessMngr == null) { //код } 

4) The introduction of variables and the division into semantic blocks. As a result, blocks can be changed independently, and most importantly, it is much easier to read and perceive such code. One block - one functionality. A frequent case is search with processing. It is necessary to break the code into separate semantic blocks: element search, processing.

 for (int i=0;i<array.length;i++) { if (array[i] == findItem) { //обработка array[i] break; } } 

at

 for(int i=0;i<array.length;i++) { if(array[i] == findItem) { foundItem =array[i]; break; } } if (foundItem != null) { //обработка foundItem } 

Rule 3. We get rid of indexers and hits through properties.

Indexer is the operation of accessing an array element at index arr [index].

In the process of perception of the code, the brain operates with [] characters as delimiters, and the indexer as an expression.

 function updateActiveColumnsSetting(fieldsGroups) { var result = {}; for (var i = 0; i < fieldsGroups.length; i++) { var fields = fieldsGroups[i].fields; for (var j = 0; j < fields.length; j++) { if (!result[fieldsGroups[i].groupName]) { result[fieldsGroups[j].groupName] = {}; } result[fieldsGroups[i].groupName][fields[j].field] = createColumnAttributes(j, fields[j].isActive); } } return JSON.stringify(result); } 

Indexer - a place of frequent errors. Due to short index names, it is very easy to confuse I, j or k.

In the example above, in the line result [fieldsGroups [j] .groupName] = {}; mistake:
j is used instead of i.

In order to find out where the ith value is used, it is necessary to:

1) visually select an array variable

 function updateActiveColumnsSetting(fieldsGroups) { var result = {}; for (var i = 0; i < fieldsGroups.length; i++) { var fields = fieldsGroups[i].fields; for (var j = 0; j < fields.length; j++) { if (!result[fieldsGroups[i].groupName]) { result[fieldsGroups[j].groupName] = {}; } result[fieldsGroups[i].groupName][fields[j].field] = createColumnAttributes(j, fields[j].isActive); } } return JSON.stringify(result); } 

2) analyze each occurrence for the use of the desired indexer I, j, i-1, j-1, etc., keeping in perception the place of use of the indexers and the already identified circulation.
Selecting the indexer into a variable, reduce the number of dangerous places, and we can easily use the brain to perceive the variable, without the need for memorization.

After processing:

 function updateActiveColumnsSetting(fieldsGroups) { var columnsGroups = {}; for (var i = 0; i < fieldsGroups.length; i++) { var fieldsGroup = fieldsGroups[i]; var groupName = fieldsGroup.groupName; var columnsGroup = columnsGroups[groupName]; if (!columnsGroup) { columnsGroup = columnsGroups[groupName] = {}; } var fields = fieldsGroup.fields; for (var j = 0; j < fields.length; j++) { var fieldInfo = fields[j]; columnsGroup[fieldInfo.field] = createColumnAttributes(j, field.isActive); } } return columnsGroups; } 

Visual selection is much easier, and modern development environments and editors help by highlighting the substrings in different parts of the code.

 function updateActiveColumnsSetting(fieldsGroups) { var columnsGroups = {}; for (var i = 0; i < fieldsGroups.length; i++) { var fieldsGroup = fieldsGroups[i]; var groupName = fieldsGroup.groupName; var columnsGroup = columnsGroups[groupName]; if (!columnsGroup) { columnsGroup = columnsGroups[groupName] = {}; } var fields = fieldsGroup.fields; for (var j = 0; j < fields.length; j++) { var fieldInfo = fields[j]; columnsGroup[fieldInfo.field] = createColumnAttributes(j, field.isActive); } } return columnsGroups; } 

Rule 4. We group the blocks by meaning.

We use the psychological effect of perception - “The proximity effect”: closely spaced figures are combined in perception. Get the code prepared for analysis and synthesis in the process of perception, and reduce the amount of information stored in memory, you can arrange a number of lines, united by meaning or similar in functionality, separating them with an empty line.

Before:

 foreach(var abcFactInfo in abcFactInfos) { var currentFact = abcInfoManager.GetFact(abcFactInfo); var percentage = GetPercentage(summaryFact, currentFact); abcInfoManager.SetPercentage(abcFactInfo, percentage); accumPercentage += percentage; abcInfoManager.SetAccumulatedPercentage(abcFactInfo, accumPercentage); var category = GetAbcCategory(accumPercentage, categoryDictionary); abcInfoManager.SetCategory(abcFactInfo, category); } 

After:

 foreach (var abcFactInfo in abcFactInfos) { var currentFact = abcInfoManager.GetFact (abcFactInfo); var percentage = GetPercentage(summaryFact, currentFact); accumPercentage += percentage; var category = GetAbcCategory(accumPercentage, categoryDictionary); abcInfoManager.SetPercentage(abcFactInfo, percentage); abcInfoManager.SetAccumulatedPercentage(abcFactInfo, accumPercentage); abcInfoManager.SetCategory(abcFactInfo, category); } 

In the upper example, 7 blocks, in the lower 3: getting values, accumulating in a loop, setting manager properties.

Indentation is good to allocate places that are worth paying attention to. So strings

 accumPercentage += percentage; var category = GetAbcCategory(accumPercentage, categoryDictionary); 

In addition to dependence on previous calculations, they accumulate values ​​in the accumulatedPercentage variable. To emphasize the differences, the code is indented.

One of the special cases of the rule application is the declaration of local variables as close as possible to the place of use.

Rule 5. Following the principle of uniqueness of responsibility.

This is the first principle of SOLID in OOP, but in addition to classes it can be applied to projects, modules, functions, code blocks, project teams or individual developers. When asking who or what is responsible for this area, it is immediately clear who or what. One connection is always simple. Each class is summarized to a single concept, phrase, metaphor. As a result, less to remember, the process of perception is easier and more efficient.

In conclusion, a comprehensive example:

 private PartnerState GetPartnerStateForUpdate( PartnerActivityInfo partner, Dictionary<int, PartnerState> currentStates, Dictionary<int, PartnerState> prevStates) { PartnerState updatingState; if (prevStates.ContainsKey(partner.id)) { if (currentStates.ContainsKey(partner.id)) { var prevState = prevStates[partner.id]; updatingState = currentStates[partner.id]; //Код 1 } else { //Код 2 } } else if (currentStates.ContainsKey(partner.id)) { updatingState = currentStates[partner.id]; } else { throw new Exception(string.Format("Для партнера {0} не найдено текущее и предыдущее состояние на месяц {1}", partner.id, month)); } return updatingState; } 

After replacing the ContainsKe indexers, inverting the branching, selecting the method and reducing the nesting levels, it turned out:

 private PartnerState GetPartnerStateForUpdate( PartnerActivityInfo partner, Dictionary<int, PartnerState> currentStates, Dictionary<int, PartnerState> prevStates) { PartnerState currentState = null; PartnerState prevState = null; prevStates.TryGetValue(partner.id, out prevState); currentStates.TryGetValue(partner.id, out currentState); currentState = CombineStates(currentState, prevState); return currentState; } private PartnerState CombineStates( PartnerState currentState, PartnerState prevState) { if (currentState == null && prevState == null) { throw new Exception(string.Format( "Для партнера {0} не найдено текущее и предыдущее состояние на месяц {1}" , partner.id, month)); } if (currentState == null) { //Код 1 } else if (prevState != null) { //Код } return currentState; } 

The first function is responsible for obtaining states from dictionaries, the second for combining them into a new one.

The presented rules are simple, but in combination give a powerful tool to simplify the code and reduce the load on memory during the development process. Always follow them will not work, but if you realize that you do not understand the code, the easiest way is to start with them. They have helped the author more than once in the most difficult cases.

Source: https://habr.com/ru/post/436160/