What is software complexity and how can you manage it?

What is software complexity and how can you manage it?

These are the slides from LoopConf 2018.

As WordPress developers, we spend a lot of time writing code. We also spend a lot of time maintaining that code. But how often do you go back to it and find out it’s become this tangled web that you almost can’t understand?

This happens to us more often than we care to admit! But do you know how it happened? It’s because you increased the complexity of your code and it became hard to know what it did.

Software complexity isn’t a well-known topic in the WordPress world. That said, a better understanding of it can lead to a noticeable increase in the quality of your code. It makes it less fragile and prone to bugs.

This talk will go over the basics of software complexity and its impact on your code. We’ll also look at concrete steps that you can take to manage it. This will help you write code that’s easier to understand and maintain.

You can read the companion article at:
https://carlalexander.ca/what-is-software-complexity/

5a4758faa5ba6c1322bdfb0f6ebcf56c?s=128

Carl Alexander

February 23, 2018
Tweet

Transcript

  1. What is software complexity and how can you manage it?

  2. Carl Alexander

  3. @twigpress

  4. carlalexander.ca

  5. None
  6. “How did this happen? How did this code get so

    messy?”
  7. Culprit is software complexity

  8. Not a topic that we’re familiar with when we start

    coding
  9. Focused on creating code that works

  10. Not thinking about maintaining it

  11. Software complexity makes this hard

  12. What is software complexity?

  13. Complexity is a measurement

  14. How much does your code interact with other code?

  15. Multiple ways to measure this

  16. Cyclomatic complexity

  17. Better-known complexity measurement method

  18. Measures the number of "linearly independent paths" through a piece

    of code
  19. None
  20. function insert_default_value($mixed) { if (empty($mixed)) { $mixed = 'value'; }

    return $mixed; }
  21. function insert_default_value($mixed) { if (empty($mixed)) { $mixed = 'value'; }

    return $mixed; }
  22. function insert_default_value($mixed) { if (empty($mixed)) { $mixed = 'value'; }

    return $mixed; }
  23. function insert_default_value($mixed) { if (empty($mixed)) { $mixed = 'value'; }

    return $mixed; }
  24. Use control flow graph to represents paths in our code

  25. None
  26. 1 2 3 4

  27. 1 2 3 4

  28. Cyclomatic complexity equation: M = E − N + 2

  29. Cyclomatic complexity equation: M = 4 − 4 + 2

  30. 1 2

  31. This is a lot of work! (Ain’t nobody got time

    for that!)
  32. Easy mode calculation

  33. function insert_default_value($mixed) // 1 { if (empty($mixed)) { // 2

    $mixed = 'value'; } return $mixed; }
  34. function insert_default_value($mixed) // 1 { if (!is_string($mixed) || empty($mixed)) {

    // 2,3 $mixed = 'value'; } return $mixed; }
  35. What's a good cyclomatic complexity value?

  36. Low complexity (1 - 4)

  37. Moderate complexity (5 - 7)

  38. High complexity (8 - 10)

  39. “Ludicrous!” complexity (10+)

  40. Issues with cyclomatic complexity

  41. Treats if, while, for and case statements as identical

  42. A for loop is the same the 1st or 10,000th

    time
  43. An if statement alters the path through your code

  44. Different effect on complexity

  45. Doesn't account for nesting

  46. Is nested for loops the same as linear for loops?

  47. Cognitive complexity not taken into consideration

  48. NPATH

  49. Solves the shortcomings of cyclomatic complexity

  50. Counts the number of “acyclic execution paths” in your code

  51. None
  52. Counts the number of “unique paths” in your code

  53. function insert_default_value($mixed) { if (!is_string($mixed) || empty($mixed)) { $mixed =

    'value'; } return $mixed; }
  54. How many unique paths does our function have?

  55. None
  56. None
  57. None
  58. Easy to visualize so far!

  59. function insert_default_value($mixed) { if ($mixed instanceof ToStringInterface) { $mixed =

    $mixed->to_string(); } if (!is_string($mixed) || empty($mixed)) { $mixed = 'value'; } return $mixed; }
  60. function insert_default_value($mixed) { if ($mixed instanceof ToStringInterface) { $mixed =

    $mixed->to_string(); } if (!is_string($mixed) || empty($mixed)) { $mixed = 'value'; } return $mixed; }
  61. How many paths are there now?

  62. (Answer: 6) How many paths are there now?

  63. Statements are multiplicative

  64. 2 * 3 = 6

  65. Conditionals are dangerous

  66. Unique paths increase quickly

  67. Rarely have two conditionals

  68. What if we had a dozen?

  69. What if we had a dozen? (2¹² = 4096 unique

    paths!)
  70. How many unique paths should your code have?

  71. Tools warn you at 200 paths

  72. 50 paths is more reasonable

  73. How to reduce complexity

  74. Break your large function or method into smaller ones

  75. Example: Going from 12 to 6 statements

  76. Example: Going from 12 to 6 statements (4096 => 2⁶

    = 64 unique paths)
  77. Not easy to split functions or methods in two

  78. More common to extract small blocks of code

  79. What code is good to extract?

  80. Easiest is code that logically belongs together

  81. function create_reminder($name, $date = '') { // ... $date_format =

    'Y-m-d H:i:s'; $formatted_date = DateTime::createFromFormat($date_format, $date); if (!empty($date) && (!$formatted_date || $formatted_date->format($date_format) != $date) ) { throw new InvalidArgumentException(); } // ... }
  82. function create_reminder($name, $date = '') { // ... $date_format =

    'Y-m-d H:i:s'; $formatted_date = DateTime::createFromFormat($date_format, $date); if (!empty($date) && (!$formatted_date || $formatted_date->format($date_format) != $date) ) { throw new InvalidArgumentException(); } // ... }
  83. function create_reminder($name, $date = '') { // ... if (!empty($date)

    && !is_reminder_date_valid($date)) { throw new InvalidArgumentException(); } // ... } function is_reminder_date_valid($date) { $date_format = 'Y-m-d H:i:s’; $formatted_date = \DateTime::createFromFormat($date_format, $date); return $formatted_date && $formatted_date->format($date_format) === $date; }
  84. function create_reminder($name, $date = '') { // ... if (!empty($date)

    && !is_reminder_date_valid($date)) { throw new InvalidArgumentException(); } // ... } function is_reminder_date_valid($date) { $date_format = 'Y-m-d H:i:s'; $formatted_date = \DateTime::createFromFormat($date_format, $date); return $formatted_date && $formatted_date->format($date_format) === $date; }
  85. Large conditional statements

  86. function send_response(array $response) { if (empty($response['headers']) || !is_array($response['headers']) || empty($response[‘headers']['status'])

    ) { throw new \InvalidArgumentException(); } // ... }
  87. function send_response(array $response) { if (empty($response['headers']) || !is_array($response['headers']) || empty($response[‘headers']['status'])

    ) { throw new \InvalidArgumentException(); } // ... }
  88. function send_response(array $response) { if (!response_has_status_header($response)) { throw new \InvalidArgumentException();

    } // ... } function response_has_status_header(array $response) { return !empty($response['headers']) && is_array($response['headers']) && !empty($response['headers']['status']); }
  89. function send_response(array $response) { if (!response_has_status_header($response)) { throw new \InvalidArgumentException();

    } // ... } function response_has_status_header(array $response) { return !empty($response['headers']) && is_array($response['headers']) && !empty($response['headers']['status']); }
  90. Combining conditionals together

  91. function insert_default_value($mixed) { if ($mixed instanceof ToStringInterface) { $mixed =

    $mixed->to_string(); } if (!is_string($mixed) || empty($mixed)) { $mixed = 'value'; } return $mixed; }
  92. function insert_default_value($mixed) { if ($mixed instanceof ToStringInterface) { $mixed =

    $mixed->to_string(); } elseif (!is_string($mixed) || empty($mixed)) { $mixed = 'value'; } return $mixed; }
  93. function insert_default_value($mixed) { if ($mixed instanceof ToStringInterface) { $mixed =

    $mixed->to_string(); } elseif (!is_string($mixed) || empty($mixed)) { $mixed = 'value'; } return $mixed; }
  94. Are we just hiding the problem?

  95. Evaluating the complexity of a function or method

  96. Not evaluating the complexity of the software as a whole

  97. Correlation between the two

  98. Tools

  99. Calculating complexity values isn’t practical

  100. Tools needed to scan your code

  101. Command-line tools

  102. Good starting point

  103. PHP code sniffer

  104. Enforces coding standards

  105. Includes cyclomatic complexity (But not NPATH)

  106. PHP mess detector

  107. Detects problems in your code

  108. Supports both measurement methods

  109. Consider using both

  110. Code quality services

  111. Good for teams

  112. Connects to your git repository

  113. Continuously analyzes your code

  114. Enforces code quality

  115. Codacity Code Climate Scrutinizer

  116. Complexity isn't that complex

  117. Intimidating topic

  118. Theory and language make it seem complicated

  119. All about the size of your functions and methods

  120. Mathematics only there to quantify the effect

  121. Not necessary to reduce complexity

  122. Just focus on keeping your functions and methods small

  123. Thank you!