Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Easy and complex

Easy and complex

8c3a2ebf7c2b84f8390d99c7bf8c8a48?s=128

Sobolev Nikita

February 08, 2020
Tweet

Transcript

  1. Никита Соболев github.com/sobolevn 1

  2. sobolevn.me 2

  3. >_ X Просто и сложно 3

  4. 4

  5. Почти всегда "плохой" = "сложный" 5

  6. 6

  7. Какой же код на самом деле сложный? 7

  8. "Я не знаю, что делает функция на 500 строк, но

    могу сразу сказать, что она слишком сложная" – Неизвестный автор 8
  9. Метрики против ощущений 9

  10. >_ X Структурная сложность 10

  11. Цикломатическая сложность или McCabe complexity 11

  12. M = E − N + 2P M = цикломатическая

    сложность, E = количество рёбер в графе, N = количество узлов в графе, P = количество компонент связности. 12
  13. 9 − 8 + 2 × 1 = 3 13

  14. int sumOfPrimes(int max) { // +1 int total = 0;

    OUT: for (int i = 1; i <= max; ++i) { for (int j = 2; j < i; ++j) { if (i % j == 0) { continue OUT; } } total += i; } return total; } 14
  15. int sumOfPrimes(int max) { // +1 int total = 0;

    OUT: for (int i = 1; i <= max; ++i) { // +1 for (int j = 2; j < i; ++j) { if (i % j == 0) { continue OUT; } } total += i; } return total; } 15
  16. int sumOfPrimes(int max) { // +1 int total = 0;

    OUT: for (int i = 1; i <= max; ++i) { // +1 for (int j = 2; j < i; ++j) { // +1 if (i % j == 0) { continue OUT; } } total += i; } return total; } 16
  17. int sumOfPrimes(int max) { // +1 int total = 0;

    OUT: for (int i = 1; i <= max; ++i) { // +1 for (int j = 2; j < i; ++j) { // +1 if (i % j == 0) { // +1 continue OUT; } } total += i; } return total; } // Cyclomatic Complexity 4 17
  18. Реализации > https://github.com/pycqa/mccabe > https://github.com/rubik/radon > https://github.com/tonybaloney/ wily > https://github.com/PyCQA/pylint

    > https://github.com/wemake- services/wemake-python-styleguide 18
  19. Когнитивная сложность 19

  20. Могут ли ваш код читать люди? 20

  21. String getWords(int number) { // +1 switch (number) { case

    1: // +1 return "one"; case 2: // +1 return "a couple"; case 3: // +1 return "several"; default: return "lots"; } } // Cyclomatic Complexity 4 21
  22. int sumOfPrimes(int max) { // +1 int total = 0;

    OUT: for (int i = 1; i <= max; ++i) { // +1 for (int j = 2; j < i; ++j) { // +1 if (i % j == 0) { // +1 continue OUT; } } total += i; } return total; } // Cyclomatic Complexity 4 22
  23. Метрики против ощущений 23

  24. G. Ann Campbell sonarsource.com/docs/CognitiveComplexity.pdf 24

  25. 25

  26. > Increment when there is a break in the linear

    (top-to-bottom, left- to-right) flow of the code 25
  27. > Increment when there is a break in the linear

    (top-to-bottom, left- to-right) flow of the code > Increment when structures that break the flow are nested 25
  28. > Increment when there is a break in the linear

    (top-to-bottom, left- to-right) flow of the code > Increment when structures that break the flow are nested > Ignore "shorthand" structures that readably condense multiple lines of code into one 25
  29. // Cyclomatic Cognitive String getWords(int number) { // +1 switch

    (number) { // +1 case 1: // +1 return "one"; case 2: // +1 return "a couple"; default: // +1 return "lots"; } } // =4 =1 26
  30. // Cyc Cog int sumOfPrimes(int max) { // +1 int

    total = 0; OUT: for (int i = 1; i <= max; ++i) { // +1 +1 for (int j = 2; j < i; ++j) { // +1 +2 (nesting=1) if (i % j == 0) { // +1 +3 (nesting=2) continue OUT; // +1 } } total += i; } return total; } // =4 =7 27
  31. String getWords(int number) { switch (number) { case 1: return

    "one"; case 2: return "a couple"; case 3: return "several"; default: return "lots"; } } Просто 28
  32. int sumOfPrimes(int max) { int total = 0; OUT: for

    (int i = 1; i <= max; ++i) { for (int j = 2; j < i; ++j) { if (i % j == 0) { continue OUT; } } total += i; } return total; } Сложно 29
  33. Ужасно! 30

  34. Реализации > https://github.com/Melevir/ cognitive_complexity > https://github.com/wemake- services/wemake-python-styleguide 31

  35. Jones complexity 32

  36. print(name_with_meaning, second_name_with_meaning, third) print(first * 5.323 * 2, trans(*matrix), show(matrix,

    2)) 33
  37. AST 34

  38. print(name_with_meaning, second_name_with_meaning, third) 35

  39. <_ast.Module> ┗━ body ┣━ [0] <_ast.Expr> ┃ ┗━ value: <_ast.Call>

    ┃ ┣━ args ┃ ┃ ┣━ [0] <_ast.Name> ┃ ┃ ┃ ┗━ id: first_long_name_with_meaning ┃ ┃ ┣━ [1] <_ast.Name> ┃ ┃ ┃ ┗━ id: second_very_long_name_with_meaning ┃ ┃ ┗━ [2] <_ast.Name> ┃ ┃ ┗━ id: third ┃ ┗━ func: <_ast.Name> ┃ ┗━ id: print 36
  40. <_ast.Module> ┗━ body ┣━ [0] <_ast.Expr> ┃ ┗━ value: <_ast.Call>

    ┃ ┣━ args ┃ ┃ ┣━ [0] <_ast.Name> ┃ ┃ ┃ ┗━ id: first_long_name_with_meaning ┃ ┃ ┣━ [1] <_ast.Name> ┃ ┃ ┃ ┗━ id: second_very_long_name_with_meaning ┃ ┃ ┗━ [2] <_ast.Name> ┃ ┃ ┗━ id: third ┃ ┗━ func: <_ast.Name> ┃ ┗━ id: print 37
  41. <_ast.Module> ┗━ body ┣━ [0] <_ast.Expr> ┃ ┗━ value: <_ast.Call>

    ┃ ┣━ args ┃ ┃ ┣━ [0] <_ast.Name> ┃ ┃ ┃ ┗━ id: first_long_name_with_meaning ┃ ┃ ┣━ [1] <_ast.Name> ┃ ┃ ┃ ┗━ id: second_very_long_name_with_meaning ┃ ┃ ┗━ [2] <_ast.Name> ┃ ┃ ┗━ id: third ┃ ┗━ func: <_ast.Name> ┃ ┗━ id: print 38
  42. <_ast.Module> ┗━ body ┣━ [0] <_ast.Expr> ┃ ┗━ value: <_ast.Call>

    ┃ ┣━ args ┃ ┃ ┣━ [0] <_ast.Name> ┃ ┃ ┃ ┗━ id: first_long_name_with_meaning ┃ ┃ ┣━ [1] <_ast.Name> ┃ ┃ ┃ ┗━ id: second_very_long_name_with_meaning ┃ ┃ ┗━ [2] <_ast.Name> ┃ ┃ ┗━ id: third ┃ ┗━ func: <_ast.Name> ┃ ┗━ id: print 39
  43. <_ast.Module> ┗━ body ┣━ [0] <_ast.Expr> ┃ ┗━ value: <_ast.Call>

    ┃ ┣━ args ┃ ┃ ┣━ [0] <_ast.Name> ┃ ┃ ┃ ┗━ id: first_long_name_with_meaning ┃ ┃ ┣━ [1] <_ast.Name> ┃ ┃ ┃ ┗━ id: second_very_long_name_with_meaning ┃ ┃ ┗━ [2] <_ast.Name> ┃ ┃ ┗━ id: third ┃ ┗━ func: <_ast.Name> ┃ ┗━ id: print 40
  44. print(first * 5.323 * 2, trans(*matrix), show(matrix, 2)) 41

  45. <_ast.Module> ┗━ body ┗━ [1] <_ast.Expr> ┗━ value: <_ast.Call> ┣━

    args ┃ ┣━ [0] <_ast.BinOp> ┃ ┃ ┣━ left: <_ast.BinOp> ┃ ┃ ┃ ┣━ left: <_ast.Name> ┃ ┃ ┃ ┃ ┗━ id: first ┃ ┃ ┃ ┣━ op: <_ast.Mult> ┃ ┃ ┃ ┗━ right: <_ast.Constant> ┃ ┃ ┃ ┗━ value: 5 ┃ ┃ ┣━ op: <_ast.Add> ┃ ┃ ┗━ right: <_ast.BinOp> ┃ ┃ ┣━ left: <_ast.Attribute> ┃ ┃ ┃ ┣━ attr: pi ┃ ┃ ┃ ┗━ value: <_ast.Name> ┃ ┃ ┃ ┗━ id: math ┃ ┃ ┣━ op: <_ast.Mult> ┃ ┃ ┗━ right: <_ast.Constant> ┃ ┃ ┗━ value: 2 ┃ ┣━ [1] <_ast.Call> ┃ ┃ ┣━ args ┃ ┃ ┃ ┗━ [0] <_ast.Starred> ┃ ┃ ┃ ┗━ value: <_ast.Name> ┃ ┃ ┃ ┗━ id: matrix ┃ ┃ ┗━ func: <_ast.Attribute> ┃ ┃ ┗━ value: <_ast.Name> ┃ ┃ ┗━ id: matrix ┃ ┗━ [2] <_ast.Call> ┃ ┣━ args ┃ ┃ ┣━ [0] <_ast.Name> ┃ ┃ ┃ ┗━ id: matrix ┃ ┃ ┗━ [1] <_ast.Constant> ┃ ┃ ┗━ value: 2 ┃ ┗━ func: <_ast.Attribute> ┃ ┣━ attr: show ┃ ┗━ value: <_ast.Name> ┃ ┗━ id: display ┗━ func: <_ast.Name> ┗━ id: print 42
  46. Реализации > https://github.com/Miserlou/ JonesComplexity > https://github.com/wemake- services/wemake-python-styleguide 43

  47. Длина строки – метрика сложности, 80 лучше всего 44

  48. Мы разобрались со строчками! 45

  49. >_ X Синтаксическая сложность 46

  50. Метрики сложности структур языка 47

  51. Структуры > OverusedExpressionViolation > OverusedStringViolation > TooLongYieldTupleViolation > TooLongCompareViolation >

    TooLongTryBodyViolation > TooDeepAccessViolation > TooLongCallChainViolation > TooDeepNestingViolation > TooManyConditionsViolation > TooManyElifsViolation > TooManyForsInComprehensionViolation > TooManyExceptCasesViolation 48
  52. Функции > TooManyLocalsViolation > TooManyArgumentsViolation > TooManyReturnsViolation > TooManyExpressionsViolation >

    TooManyDecoratorsViolation > TooManyAwaitsViolation > TooManyAssertsViolation 49
  53. Классы > TooManyMethodsViolation > TooManyBaseClassesViolation > TooManyDecoratorsViolation > TooManyPublicAttributesViolation 50

  54. Модули > TooManyImportsViolation > TooManyImportedNamesViolation > TooManyModuleMembersViolation > JonesScoreViolation >

    CognitiveModuleComplexityViolation 51
  55. Модули > TooManyImportsViolation > TooManyImportedNamesViolation 52

  56. 53

  57. sobolevn.me/2019/10/complexity-waterfall 54

  58. Процесс: 55

  59. Процесс: > Пишем простые блоки кода 55

  60. Процесс: > Пишем простые блоки кода > В какой-то момент

    сложность переполняется 55
  61. Процесс: > Пишем простые блоки кода > В какой-то момент

    сложность переполняется > Рефакторим 55
  62. Реализации > https://github.com/wemake- services/wemake-python-styleguide 56

  63. Sobolev's complexity or debug complexity * Original idea by Tin

    Marković 57
  64. Сложность, ломающая возможность навигации 58

  65. getattr(your_object, your_property) some.__dict__[key] type('Name', (cls,), properties) raise ValueError() yield async

    / await globals()[some_var] *args, **kwargs metaclass= 59
  66. Реализации > https://github.com/wemake- services/wemake-python-styleguide (WIP 0.15) 60

  67. Не все можно измерить. Однако 61

  68. >_ X Концептуальная сложность 62

  69. Что такое монада? 63

  70. None
  71. None
  72. -- the type of monad m data m a =

    ... -- return takes a value and embeds it in the monad. return :: a -> m a -- bind is a function that combines m a with a computation -- monad instance m b (>>=) :: m a -> (a -> m b) -> m b
  73. def fetch_user_profile(user_id: int) -> IOResultE['User']: return flow( user_id, _make_request, IOResult.lift_result(bind(_parse_json)),

    ) @impure_safe def _make_request(user_id: int) -> requests.Response: response = requests.get('/users/{0}'.format(user_id)) response.raise_for_status() return response @safe def _parse_json(response: requests.Response) -> 'User': return response.json() 67
  74. А как насчет типизированного функционального внедрения зависимостей? sobolevn.me/2020/02/typed-functional-dependency-injection 68

  75. А как насчет функциональных объектов? sobolevn.me/2019/03/enforcing-srp 69

  76. "Любую архитектурную проблему можно решить добавлением еще одного слоя абстракции"

    – Неизвестный автор 70
  77. "Кроме проблемы количества слоев абстракции" – Тот же автор 71

  78. Что делать? 72

  79. Что делать? > Учить людей! 72

  80. Что делать? > Учить людей! > Страдать 72

  81. >_ X Доменная сложность 73

  82. Насколько сложна ваша предметная область? 74

  83. Насколько сложна ваша предметная область? > Порог входа 74

  84. Насколько сложна ваша предметная область? > Порог входа > Термины

    и их количество 74
  85. Насколько сложна ваша предметная область? > Порог входа > Термины

    и их количество > Процессы 74
  86. Насколько сложна ваша предметная область? > Порог входа > Термины

    и их количество > Процессы > Правила 74
  87. Насколько сложна ваша предметная область? > Порог входа > Термины

    и их количество > Процессы > Правила > Варианты использования 74
  88. Создание общего языка и терминов 75

  89. 76

  90. Визуализация процессов 77

  91. 78

  92. 79

  93. 80

  94. Создание правил 81

  95. Type Driven Development 82

  96. sum : (single : Bool) -> isSingleton single -> Nat

    sum True x = x sum False [] = 0 sum False (x :: xs) = x + sum False xs 83
  97. 84

  98. НО В ПИТОНЕ НЕТ ТИПОВ 85

  99. int + int = int 86

  100. В ПИТОНЕ ЕСТЬ ТИПЫ 87

  101. Callable[[List[int]], List[str]] 88

  102. Callable[[List[int]], List[str]] • Выбросит ли она исключение? 88

  103. Callable[[List[int]], List[str]] • Выбросит ли она исключение? • Является ли

    она чистой? 88
  104. Callable[[List[int]], List[str]] • Выбросит ли она исключение? • Является ли

    она чистой? • Будет ли добиться в базу или http? 88
  105. Callable[[List[int]], List[str]] 89

  106. Callable[[List[int]], List[str]] • Выбросит ли она исключение? Да? Result[List[int], Exception]

    89
  107. Callable[[List[int]], List[str]] • Выбросит ли она исключение? Да? Result[List[int], Exception]

    • Является ли она нечистой? Да? IO[List[int]] 89
  108. Callable[[List[int]], List[str]] • Выбросит ли она исключение? Да? Result[List[int], Exception]

    • Является ли она нечистой? Да? IO[List[int]] • Все вместе? Конечно! IO[Result[List[int], Exception]] 89
  109. def fetch_user_profile(user_id: int) -> IOResultE['User']: return flow( user_id, _make_request, IOResult.lift_result(bind(_parse_json)),

    ) @impure_safe def _make_request(user_id: int) -> requests.Response: response = requests.get('/users/{0}'.format(user_id)) response.raise_for_status() return response @safe def _parse_json(response: requests.Response) -> 'User': return response.json() 90
  110. def fetch_user_profile(user_id: int) -> IOResultE['User']: def _make_request( user_id: int, )

    -> IOResultE[requests.Response]: response = requests.get('/users/{0}'.format(user_id)) response.raise_for_status() return response def _parse_json( response: requests.Response, ) -> ResultE['User']: return response.json() 91
  111. def fetch_user_profile(user_id: int) -> IOResultE['User']: return flow( user_id, _make_request, IOResult.lift_result(bind(_parse_json)),

    ) @impure_safe def _make_request(user_id: int) -> requests.Response: response = requests.get('/users/{0}'.format(user_id)) response.raise_for_status() return response @safe def _parse_json(response: requests.Response) -> 'User': return response.json() 92
  112. def fetch_user_profile(user_id: int) -> IOResultE['User']: return flow( user_id, _make_request, IOResult.lift_result(bind(_parse_json)),

    ) @impure_safe def _make_request(user_id: int) -> requests.Response: response = requests.get('/users/{0}'.format(user_id)) response.raise_for_status() return response @safe def _parse_json(response: requests.Response) -> 'User': return response.json() 93
  113. dry-python/returns Делаем неявное – явным 94

  114. Contract Driven Development 95

  115. values := << 1, 2, 4, 8 >> -- Sum

    of (index * values [index]). across values as i from sum := 0 loop sum := sum + i.cursor_index * i.item end 96
  116. eiffel.org 97

  117. @deal.pre(lambda *args: all(arg > 0 for arg in args)) @deal.post(lambda

    result: result > 5) def sum_positive(*args): return sum(args) sum_positive(1, 2, 3, 4) # 10 sum_positive(1, 2, -3, 4) # PreContractError: sum_positive(1, 2) # PostContractError: 98
  118. @deal.inv(lambda post: post.likes >= 0) class Post: likes = 0

    post = Post() post.likes = 10 post.likes = -10 # InvContractError: 99
  119. import deal deal.module_load(deal.silent) print(1) # contract violation! 100

  120. https://github.com/ life4/deal 101

  121. Layer Driven Development

  122. Слои 103

  123. Слои 104

  124. [importlinter] root_package = django_project include_external_packages = True [importlinter:contract:layers] name =

    Layered architecture of our linter type = layers containers = django_project layers = urls views forms models logic 105
  125. [importlinter] root_package = django_project include_external_packages = True [importlinter:contract:layers] name =

    Layered architecture of our linter type = layers containers = django_project layers = urls views forms models logic 106
  126. [importlinter] root_package = django_project include_external_packages = True [importlinter:contract:layers] name =

    Layered architecture of our linter type = layers containers = django_project layers = urls views forms models logic 107
  127. [importlinter] root_package = django_project include_external_packages = True [importlinter:contract:layers] name =

    Layered architecture of our linter type = layers containers = django_project layers = urls views forms models logic 108
  128. [importlinter] root_package = django_project include_external_packages = True [importlinter:contract:layers] name =

    Layered architecture of our linter type = layers containers = django_project layers = urls views forms models logic 109
  129. Независимость 110

  130. [importlinter:contract:violation-independence] name = Independence contract for violations type = independence

    modules = django_project.billing_app django_project.auth_app django_project.orders_app django_project.statistics_app 111
  131. [importlinter:contract:violation-independence] name = Independence contract for violations type = independence

    modules = django_project.billing_app django_project.auth_app django_project.orders_app django_project.statistics_app 112
  132. [importlinter:contract:violation-independence] name = Independence contract for violations type = independence

    modules = django_project.billing_app django_project.auth_app django_project.orders_app django_project.statistics_app 113
  133. Непротекающие абстракции 114

  134. [importlinter:contract:api-restrictions] name = Forbids to import anything from dependencies type

    = forbidden source_modules = django_project.logic forbidden_modules = # Important direct and indirect dependencies: django rest_framework 115
  135. [importlinter:contract:api-restrictions] name = Forbids to import anything from dependencies type

    = forbidden source_modules = django_project.logic forbidden_modules = # Important direct and indirect dependencies: django rest_framework 116
  136. [importlinter:contract:api-restrictions] name = Forbids to import anything from dependencies type

    = forbidden source_modules = django_project.logic forbidden_modules = # Important direct and indirect dependencies: django rest_framework 117
  137. [importlinter:contract:api-restrictions] name = Forbids to import anything from dependencies type

    = forbidden source_modules = django_project.logic forbidden_modules = # Important direct and indirect dependencies: django rest_framework 118
  138. Можно писать свои контракты

  139. Описание вариантов использование и тестирование их корректности 120

  140. sobolevn.me/2019/02/engineering-guide- to-user-stories 121

  141. pypi.org/project/pytest-bdd 122

  142. Документация 123

  143. Что делать то? > Использовать DDD > Строить чистую архитектуру

    > Следить за качеством абстракций > Документировать и создавать обучающие материалы 124
  144. >_ X Сложность интерпретации 125

  145. Flamegraph показывает, насколько сложно вашему интерпретатору 126

  146. 127

  147. 128

  148. Реализации > https://docs.python.org/3/library/ profile.html > https://github.com/benfred/py-spy 129

  149. >_ X Инфраструктурная сложность 130

  150. Что есть наша инфраструктура? 131

  151. Что есть наша инфраструктура? > Количество зависимостей 131

  152. Что есть наша инфраструктура? > Количество зависимостей > Количество интеграций

    131
  153. Что есть наша инфраструктура? > Количество зависимостей > Количество интеграций

    > Количество микросервисов 131
  154. Что есть наша инфраструктура? > Количество зависимостей > Количество интеграций

    > Количество микросервисов > Тип инфраструктуры 131
  155. 132

  156. 133

  157. None
  158. None
  159. 135

  160. >_ X Выводы 136

  161. Сегодня мы многое поняли

  162. Сложность окружает нас везде! 138

  163. Человек не может следить за метриками сложности – они скрыты!

    139
  164. Автоматизируйте! 140

  165. И позвольте человеку следить за корректностью работы автоматики! 141

  166. Победите сложность! 142

  167. Полезные ссылки 143

  168. Полезные ссылки > sobolevn.me/2019/10/complexity- waterfall 143

  169. Полезные ссылки > sobolevn.me/2019/10/complexity- waterfall > github.com/wemake-services/wemake- python-styleguide 143

  170. Полезные ссылки > sobolevn.me/2019/10/complexity- waterfall > github.com/wemake-services/wemake- python-styleguide > wemake-python-stylegui.de/en/latest/

    pages/usage/violations/complexity.html 143
  171. Полезные ссылки > sobolevn.me/2019/10/complexity- waterfall > github.com/wemake-services/wemake- python-styleguide > wemake-python-stylegui.de/en/latest/

    pages/usage/violations/complexity.html > sobolevn.me/2019/02/python-exceptions- considered-an-antipattern 143
  172. Полезные ссылки > sobolevn.me/2019/10/complexity- waterfall > github.com/wemake-services/wemake- python-styleguide > wemake-python-stylegui.de/en/latest/

    pages/usage/violations/complexity.html > sobolevn.me/2019/02/python-exceptions- considered-an-antipattern > github.com/dry-python/returns 143
  173. sobolevn.me Вопросы? github.com/sobolevn 144