5 simple rules for easy to read code

Ask yourself the questions: “How to write clean, clear? How to quickly figure out someone else's code? ”

Remember the rules below and apply them!

The article does not cover the basic rules for naming variables and functions, syntactic indents, or a large-scale topic of 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 perception process 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 the facts takes place.
  3. If the fact is important - we remember, or we generalize, or we act, for example, we speak or type the code.
  4. To reduce the amount of memorized and analyzed information, a generalization is used.
  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:

  • the less elements, the less energy is spent,
  • It is required to reduce the number of perceived elements to 9,
  • use less long-term memory as little as possible: summarize or forget.

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. Reducing the number of levels - reducing the costs of fuel.

Consider ways to reduce the number of nesting levels.

1) Return management. We cut 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 discarding 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);
}
voidSendAll(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 meaningful blocks: the search for the element, the 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. Indexer - a place of frequent errors. Because of the short index names, I, j, or k can be confused easily. In the example above, in the line result [fieldsGroups [j] .groupName] = {}; An error was made: j is used instead of i. In order to find out where exactly the i-th value is used it is necessary: 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);
}










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) to analyze each occurrence to use the necessary indexer I, j, i-1, j-1, etc., keeping in perception the place of use of the indexers and already identified references.
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. Rule 4. 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: After: 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

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;
}







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);
}



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);
}





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 particular cases of the application of the rule 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, design 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, to memorize less, 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
            }
      }
      elseif (currentStates.ContainsKey(partner.id))
      {
            updatingState = currentStates[partner.id];
      }
      else
      {
             thrownew Exception(string.Format("Для партнера {0} не найдено текущее и предыдущее состояние на месяц {1}", partner.id, month));
      }
      return updatingState;
}

After replacing the ContainsKеу indexers, inverting the branching, highlighting the method and reducing the nesting levels, it turned out: The first function is responsible for getting the states from the 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 helped the author more than once in the most difficult cases.

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)
      {
            thrownew Exception(string.Format(
                    "Для партнера {0} не найдено текущее и предыдущее состояние на месяц {1}" , partner.id, month));
      }
      if (currentState == null)
      {
            //Код 1
      }
      elseif (prevState != null)
      {
            //Код 
      }
      return currentState;
}




Also popular now: