$30 off During Our Annual Pro Sale. View Details »

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/

Carl Alexander

February 23, 2018
Tweet

More Decks by Carl Alexander

Other Decks in Technology

Transcript

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

    View Slide

  2. Carl Alexander

    View Slide

  3. @twigpress

    View Slide

  4. carlalexander.ca

    View Slide

  5. View Slide

  6. “How did this happen? How did
    this code get so messy?”

    View Slide

  7. Culprit is software complexity

    View Slide

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

    View Slide

  9. Focused on
    creating code that works

    View Slide

  10. Not thinking
    about maintaining it

    View Slide

  11. Software complexity
    makes this hard

    View Slide

  12. What is software complexity?

    View Slide

  13. Complexity is a measurement

    View Slide

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

    View Slide

  15. Multiple ways to measure this

    View Slide

  16. Cyclomatic complexity

    View Slide

  17. Better-known complexity
    measurement method

    View Slide

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

    View Slide

  19. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  24. Use control flow graph to
    represents paths in our code

    View Slide

  25. View Slide

  26. 1
    2 3
    4

    View Slide

  27. 1 2
    3 4

    View Slide

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

    View Slide

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

    View Slide

  30. 1 2

    View Slide

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

    View Slide

  32. Easy mode calculation

    View Slide

  33. function insert_default_value($mixed) // 1
    {
    if (empty($mixed)) { // 2
    $mixed = 'value';
    }
    return $mixed;
    }

    View Slide

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

    View Slide

  35. What's a good cyclomatic
    complexity value?

    View Slide

  36. Low complexity
    (1 - 4)

    View Slide

  37. Moderate complexity
    (5 - 7)

    View Slide

  38. High complexity
    (8 - 10)

    View Slide

  39. “Ludicrous!” complexity
    (10+)

    View Slide

  40. Issues with
    cyclomatic complexity

    View Slide

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

    View Slide

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

    View Slide

  43. An if statement alters
    the path through your code

    View Slide

  44. Different effect on complexity

    View Slide

  45. Doesn't account for nesting

    View Slide

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

    View Slide

  47. Cognitive complexity
    not taken into consideration

    View Slide

  48. NPATH

    View Slide

  49. Solves the shortcomings of
    cyclomatic complexity

    View Slide

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

    View Slide

  51. View Slide

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

    View Slide

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

    View Slide

  54. How many unique paths
    does our function have?

    View Slide

  55. View Slide

  56. View Slide

  57. View Slide

  58. Easy to visualize so far!

    View Slide

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

    View Slide

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

    View Slide

  61. How many paths are there now?

    View Slide

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

    View Slide

  63. Statements are multiplicative

    View Slide

  64. 2 * 3 = 6

    View Slide

  65. Conditionals are dangerous

    View Slide

  66. Unique paths increase quickly

    View Slide

  67. Rarely have two conditionals

    View Slide

  68. What if we had a dozen?

    View Slide

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

    View Slide

  70. How many unique paths
    should your code have?

    View Slide

  71. Tools warn you at 200 paths

    View Slide

  72. 50 paths is more reasonable

    View Slide

  73. How to reduce complexity

    View Slide

  74. Break your large function or
    method into smaller ones

    View Slide

  75. Example:
    Going from 12 to 6 statements

    View Slide

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

    View Slide

  77. Not easy to split
    functions or methods in two

    View Slide

  78. More common to extract
    small blocks of code

    View Slide

  79. What code is good to extract?

    View Slide

  80. Easiest is code that logically
    belongs together

    View Slide

  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();
    }
    // ...
    }

    View Slide

  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();
    }
    // ...
    }

    View Slide

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

    View Slide

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

    View Slide

  85. Large conditional statements

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  90. Combining conditionals together

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  94. Are we just hiding the problem?

    View Slide

  95. Evaluating the complexity of a
    function or method

    View Slide

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

    View Slide

  97. Correlation between the two

    View Slide

  98. Tools

    View Slide

  99. Calculating complexity values
    isn’t practical

    View Slide

  100. Tools needed to scan your code

    View Slide

  101. Command-line tools

    View Slide

  102. Good starting point

    View Slide

  103. PHP code sniffer

    View Slide

  104. Enforces coding standards

    View Slide

  105. Includes cyclomatic complexity
    (But not NPATH)

    View Slide

  106. PHP mess detector

    View Slide

  107. Detects problems in your code

    View Slide

  108. Supports both
    measurement methods

    View Slide

  109. Consider using both

    View Slide

  110. Code quality services

    View Slide

  111. Good for teams

    View Slide

  112. Connects to your git repository

    View Slide

  113. Continuously analyzes your code

    View Slide

  114. Enforces code quality

    View Slide

  115. Codacity
    Code Climate
    Scrutinizer

    View Slide

  116. Complexity isn't that complex

    View Slide

  117. Intimidating topic

    View Slide

  118. Theory and language
    make it seem complicated

    View Slide

  119. All about the size of your
    functions and methods

    View Slide

  120. Mathematics only there to
    quantify the effect

    View Slide

  121. Not necessary
    to reduce complexity

    View Slide

  122. Just focus on keeping your
    functions and methods small

    View Slide

  123. Thank you!

    View Slide