-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
954 lines (786 loc) · 50 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Coupling & Cohesion</title>
<meta name="author" content="Артем"/>
<style type="text/css">
.underline { text-decoration: underline; }
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js/dist/reveal.css"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js/dist/theme/none.css" id="theme"/>
<link rel="stylesheet" href="./css/ember.css"/>
<link rel="stylesheet" href="./css/local.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/androidstudio.min.css"/>
<link rel="stylesheet" type="text/css" href="./css/local.css" />
</head>
<body>
<div class="reveal">
<div class="slides">
<section id="sec-title-slide">
<h1 class="title">Coupling & Cohesion</h1>
</section>
<section id="slide-org6b2b72b">
<h2 id="org6b2b72b" class="hidden">Coupling & Cohesion</h2>
<aside class="notes">
<p>
В основе разработки ПО лежит управление сложностью. Добавляя новую функциональность в систему мы неизбежно увеличиваем ее сложность. Но правильные подходы к разработке и хорошо построенная архитектура может замедлить рост сложности.
Проблема только в том, что никто не знает как она должна выглядеть, эта хорошо построенная архитектура.
Но существуют метрики, которые помогают нам оценить архитектуру, о двух таких метриках я бы и хотел поговорить
</p>
</aside>
<ul>
<li><b>Coupling (Связанность)</b> <br />
насколько сильно связаны друг с другом отдельные модули.</li>
<li><b>Cohesion (Сочетаемость)</b> <br />
насколько сильны связи внутри модуля.</li>
</ul>
</section>
<section>
<aside class="notes">
<p>
Используя этим метрики можно анализировать не только архитектуру отдельного приложения, но и целых систем, состоящих из многих сервисов, поэтому в определении используется слово модуль.
Сами термины были введены Ларри Константином в конце 60-ых годов, позже в 1974 детально описаны в его книге Structured Design.
Считается, что хорошо спроектированная система обладает низкой связанностью и высокой сочетаемостью (Low coupling & High cohesion).
</p>
</aside>
<div id="org110bb11" class="figure">
<p><img src="./img/CouplingVsCohesion.png" alt="CouplingVsCohesion.png" width="60%" />
</p>
</div>
</section>
<section id="slide-org4cf40b0">
<h2 id="org4cf40b0">Coupling</h2>
<aside class="notes">
<p>
Сначала поговорим о связанности, метрике, которая показывает насколько сильна связь между двумя модулями. Чем сильнее связанны модули, чем больше самих связей между ними, тем вероятнее, что изменения в одном из них затронут остальные. Высокий уровень связанности усложняет рефакторнинг и доработку кода.
Рассмотрим какие бывают типы связей в порядке от самых сильных к слабым.
</p>
</aside>
</section>
<section id="slide-orgc664805" data-auto-animate>
<h3 id="orgc664805">Content Coupling</h3>
<p>
Возникает, когда один модуль полагается на внутренние особенности реализации другого модуля.
</p>
<aside class="notes">
<p>
Связанность по содержимому является нарушением принципа инкапсуляции. Изменение имплементации одного модуля приведет к переписыванию другого.
Пример content coupling:
</p>
</aside>
</section>
<section data-auto-animate>
<div class="org-src-container">
<pre data-id="96421ed6-ea95-474f-b318-6ada909a1ebe"><code class="java" data-line-numbers>public class ShopService {
public void addNewItem(Order order, OrderItem newItem) {
order.getItems().add(newItem);
order.setSum(order.getSum() + newItem.getPrice());
}
}
</code></pre>
</div>
<aside class="notes">
<p>
<b>Проблемы:</b>
</p>
<ul>
<li>Нарушена инкапсуляция. Класс Order не отвечает за свое состояние.</li>
<li>Сложно поддерживать. При изменении структуры Order необходимо поменять все места использования.</li>
</ul>
<p>
Этот код зависит от внутреннего строения класса <code>Order</code>. Если мы захотим поменять тип списка товаров <code>items</code> внутри <code>Order</code> или, например, сумму в виде числа заменить классом <code>Money</code>, то это приведет к рефакторингу функции <code>addNewItem</code> и всех остальных мест, где используется класс <code>Order</code>.
Так же в этой функции мы делаем предположение о связях между полями внутри самого класса <code>Order</code>, что при добавлении нового продукта необходимо добавить его стоимость к сумме.
Поскольку метод <code>setSum</code> публичный, то его можно вызвать из любого места приложения, и значит в каждом из этих мест мы должны проверять, что правильно рассчитали сумму. Если же алгоритм расчета суммы меняется - все становится еще хуже.
</p>
<p>
Вместо этого модули должны обращаться друг к другу только через интерфейс. Уберем всю логику по добавлению нового товара в класс <code>Order</code>, чтобы избавится от content coupling:
</p>
</aside>
</section>
<section data-auto-animate>
<div class="org-src-container">
<pre data-id="96421ed6-ea95-474f-b318-6ada909a1ebe"><code class="java" data-line-numbers>public class ShopService {
public void addNewItem(Order order, OrderItem newItem) {
order.addItem(newItem);
}
}
</code></pre>
</div>
<aside class="notes">
<p>
Теперь класс <code>ShopService</code> не зависит от внутреннего строения класса <code>Order</code>. Класс <code>Order</code> проще тестировать и переиспользовать в других местах, так как вся логика инкапсулирована в методе <code>addItem</code> и сам класс заботится о выполнении всех бизнес правил, описывающих его возможные состояния. Меняя внутреннюю структуру класса <code>Order</code> нам не придется переписывать код, который использует этот класс.
Можно сказать, что при данном рефакторинге мы последовали закону Деметры, или принципу наименьшего знания, как его иногда называют.
</p>
</aside>
</section>
<section id="slide-org837ccd5">
<h4 id="org837ccd5">Law of Demeter</h4>
<p>
Объект должен иметь как можно меньше представления о структуре и свойствах другого объекта.
</p>
<aside class="notes">
<p>
Закон Деметры был сформулирован в 1987 году, он служит для уменьшения связанность между компонентами системы. В языках где для доступа к вложенным структурам используется точка можно упростить его до правила одной точки.
</p>
</aside>
</section>
<section>
<div id="orgf51f148" class="figure">
<p><img src="./img/demeter-law.png" alt="demeter-law.png" width="60%" />
</p>
</div>
</section>
<section id="slide-org9b92598">
<h3 id="org9b92598">Common Coupling</h3>
<p>
Возникает между модулями, когда они работают с общими данными читая и изменяя их.
</p>
<aside class="notes">
<p>
<b>Проблемы:</b>
</p>
<ul>
<li>Сложно понимать. Нет единого места, отвечающего за данные. Данные могут стать неконсистентными.</li>
<li>Сложно поддерживать. Тяжело определить кто в этот раз поменял общие данные.</li>
</ul>
<p>
Например, два класса модифицируют одну глобальную переменную или два сервиса пишут в одну и ту же таблицу в БД.
При наличие common coupling становится сложно проследить, почему значение разделяемого ресурса стало именно таким, так как оно может поменяться в любой момент и из разных мест. Это может привести к трудно отлавливаемым ошибкам. Также, при внесении изменений в структуру разделяемого ресурса, придется менять все работающие с ним модули.
Модули работающие с глобальными переменными практически не пригодны к переиспользованию.
</p>
<p>
Важно обратить внимание, что проблемы от такого вида связанности возникают в случае, если оба модуля ИЗМЕНЯЮТ общие данные. Если же в качестве общих данных глобальный набор констант или меняет общий ресурс один модуль, а остальные только читают, то такой вид связанности обычно менее болезненный.
</p>
<p>
На практике такой вид связанности возникает не часто, так как в сознание разработчиков на старте карьеры закладывают убеждение, что глобальные переменные - это плохо, а БД у каждого микросервиса должна быть своя.
</p>
</aside>
<div id="orge16d679" class="figure">
<p><img src="./img/common-coupling-ex1.png" alt="common-coupling-ex1.png" width="30%" />
</p>
</div>
</section>
<section id="slide-org05315a1">
<h3 id="org05315a1">Control Coupling</h3>
<p>
Возникает, когда один модуль управляет поведением другого, через передачу каких-то данных или флагов управления.
</p>
</section>
<section data-auto-animate>
<div class="org-src-container">
<pre data-id="365f5e64-01cf-4327-ad1c-d2a435a5bd9a"><code class="java" data-line-numbers>class OrderService {
private ReportService reportService;
public void placeOrder() {
// ...
reportService.generate(order, ReportType.PDF);
}
}
</code></pre>
</div>
<div class="org-src-container">
<pre data-id="2fddbfd1-23df-4274-bc80-9bb19b649818"><code class="java" data-line-numbers>class ReportService {
public void generate(Order data, ReportType type) {
switch (type) {
case XML:
buildXmlReport(data);
break;
case PDF:
buildPdfReport(data);
break;
}
}
private void buildXmlReport() { ... }
private void buildPdfReport() { ... }
}
</code></pre>
</div>
<aside class="notes">
<p>
<b>Проблемы:</b>
</p>
<ul>
<li>Сложно переиспользовать. Класс <code>ReportService</code> пологается на внешний код для принятия решения</li>
<li>Сложно поддерживать. При добавлении новых типов отчетов необходимо дорабатывать не только <code>ReportSrevice</code>, но и <code>OrderService</code></li>
</ul>
<p>
Проблема в том, что какой-то внешний по отношению к <code>ReportService</code> модуль управляет его поведением, это говорит о нарушении инкапсуляции, получается, что в самом модуле для этого нехватает логики. Это приводит к тому, что нельзя просто переиспользовать класс <code>ReportService</code> не копируя недостоющую логику по определению желаемого типа отчета.
</p>
<p>
<code>OrderService</code> в этой ситуации выступает как координатор, он говорит что необходимо сделать и какой результат он ожидает.
В ООП же объекты должны сами принимать решения, и содержать всю необходимую для выполнения задачи логику. Control coupling в этом примере можно убрать используя паттерн стратегия. При этом каждый алгоритм из <code>ReportService</code> мы переносим в отдельный класс. Либо инкапсулируя всю логику по определению типа отчета в класс <code>ReportService</code>.
</p>
</aside>
</section>
<section data-auto-animate>
<div class="org-src-container">
<pre data-id="365f5e64-01cf-4327-ad1c-d2a435a5bd9a"><code class="java" data-line-numbers>class OrderService {
private ReportService reportService;
public void placeOrder() {
// ...
reportService.generate(order);
}
}
</code></pre>
</div>
<div class="org-src-container">
<pre data-id="2fddbfd1-23df-4274-bc80-9bb19b649818"><code class="java" data-line-numbers>public interface ReportService {
void generate(Order order);
}
public class XmlReportBuilder implements ReportService { }
public class PdfReportBuilder implements ReportService { }
</code></pre>
</div>
<aside class="notes">
<p>
Таким образом мы изолировали каждый алгоритм в отдельном классе, что гораздо удобнее с точки зрения тестирования и понимания кода. Добавление новых типов отчетов не потребует изменения класса <code>OrderService</code>, что являлось для нас главной целью при снижении связанности.
</p>
</aside>
</section>
<section>
<aside class="notes">
<p>
Как один из примеров control coupling часто встречаются методы, принимающие <code>boolean</code> флаги, которые определяют их поведение.
Помимо связанности тут еще присутствует проблема <code>boolean blindness</code>. Догадаться что означает этот <code>true</code> без чтения кода метода абсолютно невозможно. В данном случае можно просто разделить метод на два:
</p>
</aside>
<div id="org261160e" class="figure">
<p><img src="./img/control-coupling.png" alt="control-coupling.png" width="60%" />
</p>
</div>
</section>
<section id="slide-org63e9a74">
<h3 id="org63e9a74">Stamp Coupling</h3>
<p>
Возникает, когда модули обмениваются друг с другом данными через структуру, но при этом из этой структуры модули используют не все поля.
</p>
</section>
<section data-auto-animate>
<div class="org-src-container">
<pre data-id="50749df5-cdd4-4646-ac2e-49f857ba0b79"><code class="java" data-line-numbers>class ValidatorService {
public boolean validateEmail(Customer customer) {
var email = customer.getEmail();
return EMAIL_REGEX.matcher(email).find();
}
}
</code></pre>
</div>
<div class="org-src-container">
<pre><code class="java" >class Customer {
private String firstName;
private String lastName;
private LocalDate birthDate;
private String livingAddress;
private String email;
}
</code></pre>
</div>
<aside class="notes">
<p>
<b>Проблемы:</b>
</p>
<ul>
<li>Сложно переиспользовать. Нельзя переиспользовать функцию <code>validateEmail</code> там, где нет <code>Customer</code>.</li>
<li>Сложно тестировать. Необходимо заполнить всю структуру <code>Customer</code> для вызова.</li>
<li>Сложно читать. Невозможно предсказать поведение по сигнатуре метода.</li>
</ul>
<p>
В примере видно, что в функцию <code>validateEmail</code> передается вся структура <code>Customer</code>, хотя реально из нее используется только одно поле. Такой вид связанности несет сразу несколько проблем.
</p>
<p>
Страдает читабельность, без чтения кода только по сигнатуре функции невозможно понять, какие поля структуры в ней используются, почему туда передается объект целиком.
Функцию <code>validateEmail</code> сложно переиспользовать, так как при вызове необходимо передавать всю структуру <code>Customer</code> в качестве аргумента. По этой же причине ее сложно тестировать.
</p>
<p>
Иногда такой подход приводит к превращению класса <code>Customer</code> в свалку не связанных между собой данных, для того чтобы удовлетворить сразу несколько подобных методов.
</p>
<p>
Пример выше можно переписать следующем образом:
</p>
</aside>
</section>
<section data-auto-animate>
<div class="org-src-container">
<pre data-id="50749df5-cdd4-4646-ac2e-49f857ba0b79"><code class="java" data-line-numbers>class ValidatorService {
public boolean validateEmail(String email) {
return EMAIL_REGEX.matcher(email).find();
}
}
</code></pre>
</div>
<aside class="notes">
<p>
Так мы переделали stamp coupling в data coupling.
</p>
</aside>
</section>
<section id="slide-orgc30b9fc">
<h3 id="orgc30b9fc">Data Coupling</h3>
<p>
Возникает, когда модули обмениваются друг с другом данными через структуру, при этом используется каждое поле в этой структуре.
</p>
<aside class="notes">
<p>
Этот вид связи возникает, когда один модуль передает данные в другой в виде параметров вызова функции. В отличие от stamp coupling передаются только необходимые данные. Если передается структура, то принимающий модуль должен использовать все ее поля.
Data Coupling считается слабой связью к которой следует стремиться.
</p>
</aside>
</section>
<section id="slide-org76294a9">
<h3 id="org76294a9">Message coupling</h3>
<p>
Модули общаются только через передачу сообщений или вызовы методов без параметров.
</p>
<aside class="notes">
<p>
Модули могут обмениваться сообщениями как внутри одного приложения, используя фреймворки вроде akka так и используя внешнее ПО вроде Kafka или RabbitMQ.
</p>
</aside>
</section>
<section id="slide-org944b8a3">
<h2 id="org944b8a3">Cohesion</h2>
<aside class="notes">
<p>
Сочетаемость (cohesion) — мера того, насколько функционально взаимосвязаны компоненты внутри модуля (сервиса, класса, функции). Насколько элементы внутри модуля нуждаются друг в друге.
Низкая сочетаемость внутри модуля означает, что он решает много не связанных друг с другом задач. Высокая сочетаемость означает, что весь код внутри модуля сфокусирован на решении одной конкретной задачи.
</p>
<p>
Почему это важно? Например, есть у вас задача добавить новый тип платежа. Вы открываете код и можете быстро найти место, куда нужно добавить новый код. Так как все что касается платежей у вас собрано в одном месте. При слабой сочетаемости вам придется по всей программе искать места, где нужно внести изменения.
</p>
<p>
Это чем-то похоже на принцип единства ответственности (SPR) из SOLID, сформулированный Робертом Мартином. Принцип гласит, что класс должен иметь только одну причину для изменения.
Считается, что в хорошо спроектированной программе присутствует сильная сочетаемость внутри модулей.
Рассмотрим некоторые виды сочетаемости в порядке от самых слабых к сильным.
</p>
</aside>
</section>
<section id="slide-org0957bf5">
<h3 id="org0957bf5">Coincidental cohesion</h3>
<p>
Слабейший из видов сочетаемости. Когда элементы внутри модуля собраны по случайному принципу и никак друг с другом не связаны.
</p>
<aside class="notes">
<p>
Часто возникает в классах или пакетах со словом utils в названии.
</p>
</aside>
</section>
<section id="slide-orgd0be8a4">
<h3 id="orgd0be8a4">Logical cohesion</h3>
<p>
Логическая сочетаемость возникает, когда части модуля логически делают похожие вещи, но никак друг с другом не связаны с точки зрения бизнес смысла.
</p>
<aside class="notes">
<p>
До этого мы в качестве модулей рассматривали классы. Для иллюстрации логической сочетаемости давайте поднимемся на уровень пакетов. Например, как часто открывая новый проект мы видим такую схему проектов? Вы можете сказать что делает этот сервис?
</p>
</aside>
</section>
<section>
<pre class="example" id="org0ab8a51">
📁 dao
📁 service
📁 model
☕ Application.java
</pre>
<aside class="notes">
<p>
Такая структура пакетов никак не подсказывает нам, что делает приложение. Теперь давайте заглянем внутрь пакетов и попробуем сгруппировать классы по функционалу, к которому они относятся, а не по названиям паттернов программирования.
</p>
</aside>
</section>
<section>
<div class="LEFTCOL" id="orgdae720f">
<pre class="example" id="orgd6bd7c1">
📁 dao
☕ OrderDao.java
☕ UserDao.java
☕ PostDao.java
📁 service
☕ OrderService.java
☕ UserService.java
📁 model
☕ User.java
☕ Order.java
☕ OrderItem.java
☕ OrderState.java
☕ Comment.java
☕ Post.java
☕ Application.java
</pre>
</div>
<div class="fragment (roll-in) RIGHTCOL" id="org790ec3c">
<pre class="example" id="orgc2e4209">
📁 orders
☕ Order.java
☕ OrderItem.java
☕ OrderState.java
☕ OrderDao.java
☕ OrderService.java
📁 users
☕ User.java
☕ UserDao.java
☕ UserService.java
📁 reviews
☕ Post.java
☕ Comment.java
☕ PostDao.java
☕ Application.java
</pre>
</div>
<aside class="notes">
<p>
Слева мы видим, что присутствует сильная связанность между пакетами (OrderService наверняка использует Order, OrderItem и OrderDao). И низкая сочетаемость внутри пакетов, между OrderDao и UserDao есть только логическая сочетаемость, оба класса реализуют доступ к БД, но при этом больше у них нет ничего общего. Функционально это разные области.
</p>
<p>
Деление классов на пакеты исходя из их функционального смысла лучше, так как такой код проще читать и поддерживать.
В 2011 году Роберт Мартин (Дядя Боб) ввел в обиход термин “Кричащая Архитектура”. Он утверждал, что сама архитектура приложения должна кричать о том какую функцию выполняет систем. Организация модулей слева же “кричит” только о паттернах, которые мы используем, но разве это так важно?
</p>
<p>
Конечно, в маленьком сервисе, состоящем из десятка классов все это не играет большой роли. Маленькие утилиты или небольшие сервисы вообще можно писать как угодно, пока весь их код свободно умещается в голове.
Попытки использовать в таких системах паттерны и подходы, призванные бороться со сложностью, наоборот приводят только к ее росту.
</p>
<p>
На уровне класса можно привести следующий пример логической сочетаемости, которую часто можно встретить в коде:
</p>
</aside>
</section>
<section>
<div class="org-src-container">
<pre><code class="java" >public MessageSenderService {
public void sendOrderProcessedEvent() { ... }
public void sendReportMessage() { ... }
public void sendEmailNotification() { ... }
}
</code></pre>
</div>
<aside class="notes">
<p>
Единственное, что связывает методы в этом классе, это то что все они отправляют сообщения в некую очередь и больше ничего. Почему именно это иногда становится причиной для объединения методов в один класс - сказать сложно. Особенно когда даже нет класса, который бы использовал больше одного из этих методов.
</p>
</aside>
</section>
<section id="slide-orgaa8ae5d">
<h3 id="orgaa8ae5d">Temporal cohesion</h3>
<p>
Элементы группируются в одном модуле, так как вызываются в одно время, но функционально никак друг с другом не связаны.
</p>
<aside class="notes">
<p>
Часто такой тип сочетаемости появляются у функций инициализации, собранных в одном классе. Например:
</p>
</aside>
</section>
<section>
<div class="org-src-container">
<pre><code class="java" >public interface ApplicationInitializer {
void initDatabase();
void initPrinterService();
void initFtpSerivce();
}
</code></pre>
</div>
<aside class="notes">
<p>
<b>Проблемы:</b>
</p>
<ul>
<li>Сложно переиспользовать. Функции друг с другом не связаны, зато сильно связаны с другими модулями.</li>
</ul>
<p>
Низкая сочетаемость этих методов друг с другом и сильная связанность с другими модулями приводит к проблемам с переиспользованием данного кода. Мы не можем использовать функции модуля FTP, если перед этим не подключили модуль <code>ApplicationInitializer</code> и не вызвали процедуру <code>initFtpSerivce()</code> из него.
В качестве возможного рефакторинга стоит вынести каждую функцию инициализации в соответствующий модуль и запускать ее при инициализации самого модуля.
</p>
</aside>
</section>
<section id="slide-org47e7f5f">
<h3 id="org47e7f5f">Procedural cohesion</h3>
<p>
Функции все еще слабо связаны друг с другом, но используются в одном месте, при этом порядок вызова функций имеет значение.
</p>
</section>
<section>
<div class="org-src-container">
<pre><code class="java" data-line-numbers='1-10|4|5|6|7|8'>public class RegistrationService {
public void registerUser(String email) {
validateEmail(email);
User user = createNewUser(email);
loadProfileFromFacebook(user);
checkVipStatus(user);
sendGreetings(email, user.getName());
}
}
</code></pre>
</div>
<div class="org-src-container">
<pre><code class="java" >public interface UserService {
void validateEmail(String email);
User createNewUser(String email);
void loadProfileFromFacebook(User user);
void checkVipStatus(User user);
void sendGreetings(String email, String name);
}
</code></pre>
</div>
<aside class="notes">
<p>
<b>Проблемы:</b>
</p>
<ul>
<li>Сложно сопровождать. Необходимо полностью погрузиться в код каждой функции, чтобы ничего не сломать при изменении.</li>
</ul>
<p>
Последовательность вызовов процедур <code>UserService</code> этого класса должна сохраняться, мы же не хотим зарегистрировать пользователя с невалидным email или послать приветствие до того как получили его профиль на Фейсбуке. Узнать правильный порядок вызова этих процедур можно только полностью изучив код <code>UserService</code>. Из за этого пользоваться таким классом крайне неудобно, а код, который его использует, сложно поддается рефакторингу.
</p>
<p>
Глядя на код <code>registerUser</code> сложно сказать, каким должен быть объект <code>user</code> перед вызовом очередной процедуры, или что в нем поменяется после вызова. А изменение порядка вызова процедур может сломать весь алгоритм.
Поддержка такого кода требует от программиста полной концентрации и внимания, любое неосторожное движение приведет к возникновению ошибки.
К сожалению, такой код встречается крайне часто и обладает всеми недостатками процедурного программирования.
</p>
</aside>
</section>
<section>
<div id="org581ff33" class="figure">
<p><img src="./img/proc-refactoring.png" alt="proc-refactoring.png" width="60%" />
</p>
</div>
<aside class="notes">
<p>
Правильный подход, которого нужно придерживаться, “делайте неправильные состояния невозможными”. Если какие-то состояния в вашей программе не имеют смысла напишите код так, чтобы приложение никогда не могло попасть в это состояние. Давайте посмотрим, как мы можем переписать пример выше, чтобы сделать этот код более устойчивым к ошибкам. Следующие виды связанности считаются одинаково предпочтительными
</p>
</aside>
</section>
<section id="slide-orgada848c">
<h3 id="orgada848c">Sequential cohesion</h3>
<p>
Сочетаемость по последовательности действий возникает в случае если результат работы одной части модуля является исходными данными для другой.
</p>
</section>
<section>
<aside class="notes">
<p>
Пример sequential cohesion:
</p>
</aside>
<div class="org-src-container">
<pre><code class="java" data-line-numbers='1-10|4|5|6|7|8'>public class RegistrationService {
public void registerUser(String emailStr) {
Email email = validateEmail(emailStr);
User user = createNewUser(email);
UserProfile profile = loadProfileFromFacebook(user);
VipStatus vipStatus = checkVipStatus(user);
sendGreetings(profile, vipStatus);
}
}
</code></pre>
</div>
<div class="org-src-container">
<pre><code class="java" >public interface UserService {
Email validateEmail(String email);
User createNewUser(Email email);
UserProfile loadProfileFromFacebook(User user);
VipStatus checkVipStatus(User user);
void sendGreetings(UserProfile profile, VipStatus vipStatus);
}
</code></pre>
</div>
<aside class="notes">
<p>
В данном случае последовательность вызовов функций не имеет значения, так как благодаря системе типов мы просто не сможем, их вызывать в неправильном порядке.
</p>
</aside>
</section>
<section id="slide-orgf180262">
<h3 id="orgf180262">Communication cohesion</h3>
<p>
Сочетаемость по взаимодействию возникает, когда группируется в один модуль все функции, которые работают с одними и теми же входными или выходными данными.
</p>
</section>
<section>
<aside class="notes">
<p>
Например:
</p>
</aside>
<div class="org-src-container">
<pre><code class="java" >interface OrderService {
public void addItem(Order order, Item item);
public void deleteAllItems(Order order);
public Money calculateTotalSum(Order order);
public void startDelivery(Order order);
}
</code></pre>
</div>
<aside class="notes">
<p>
Все методы принимают объект <code>Order</code>.
</p>
</aside>
</section>
<section>
<div class="org-src-container">
<pre><code class="java" >interface ComputerFactory {
public Computer newServer(Integer ram, Integer hdd, Integer cpu);
public Computer newPc(Monitor monitor, Mouse mouse, SystemUnit unit);
public Computer newNotebook(Model model);
}
</code></pre>
</div>
<aside class="notes">
<p>
Все методы возвращают объект <code>Computer</code>.
</p>
<p>
Подобные классы сосредоточены на операциях, которые можно произвести над сущностью или на разных способах как получить сущность. Их достаточно легко переиспользовать целиком в разных частях программы. Также примером сочетаемости по взаимодействию можно считать классы в ООП, так как они по определению представляют из себя набор данных и функций, которые с ними работают.
</p>
</aside>
</section>
<section>
<div class="org-src-container">
<pre><code class="java" >class User {
private Long id;
private Image avatar;
private String email;
private List<Orders> orders;
private List<Review> reviews;
public void changeAvatar(Image image) { ... }
public void resetPassword() { ... }
public void placeNewOrder(Order order) { ... }
public void postReview(Review review) { ... }
public void assignVipStatus() { ... }
}
</code></pre>
</div>
<aside class="notes">
<p>
Заметьте, что в приведенном примере у класса нет ни сеттеров, ни геттеров на каждое поле, их вообще в принципе не должно быть в ООП коде. Сеттеры подразумевают, что существует какой-то внешний, по отношению к классу код, который принимает решения на счет данных внутри этого класса, а это является нарушением принципа инкапсуляции.
Несмотря на то, что все функции в классе <code>User</code> работают с одними и теми же данными, все же функционально их можно поделить на несколько областей: управление профилем пользователя, заказами и отзывами. Если в программе эти три области разделены на отдельные модули и имеют четкие границы, то не нужно боятся в каждом модуле сделать свой класс <code>User</code> с релевантным набором функций и данных, объединенных между собой только общим идентификатором.
Не смотря на то, что физически пользователь один, в разных контекстах нас могут интересовать разные его стороны. Не делая такое разделение мы увеличиваем связанность между модулями, которые используют общий класс <code>User</code>. При этом сам класс <code>User</code> становится сложным для понимания и тестирования из за своих размеров.
</p>
</aside>
</section>
<section>
<div id="orgf65b9a9" class="figure">
<p><img src="./img/oop-at-home.png" alt="oop-at-home.png" />
</p>
</div>
</section>
<section id="slide-orgd74441e">
<h3 id="orgd74441e">Functional cohesion</h3>
<p>
Возникает, когда элементы модуля сгруппированы вместе, так как все они вносят вклад в выполнение одной и той же функции.
</p>
</section>
<section data-auto-animate>
<aside class="notes">
<p>
Давайте рассмотрим как мы можем поделить <code>OrderService</code> согласно функциям, которые он реализует.
Например, мы можем его поделить на класс, управляющий корзиной, калькулятор и класс отвечающий за доставку.
</p>
</aside>
<div class="org-src-container">
<pre data-id="cb108705-3bcc-4571-9ed8-42e0b375c1e6"><code class="java" data-line-numbers>interface OrderService {
public void addItem(Order order, Item item);
public void deleteAllItems(Order order);
public Money calculateTotalSum(Order order);
public void startDelivery(Order order);
}
</code></pre>
</div>
</section>
<section data-auto-animate>
<div class="org-src-container">
<pre data-id="cb108705-3bcc-4571-9ed8-42e0b375c1e6"><code class="java" data-line-numbers>interface OrderCart {
public void addItem(Order order, Item item);
public void deleteAllItems(Order order);
}
interface OrderCostCalculator {
public Money calculateTotalSum(Order order);
}
interface OrderDelivery {
public void startDelivery(Order order);
}
</code></pre>
</div>
<aside class="notes">
<p>
Обратите внимание, за счет этого разделения по функциональности имена наших классов стали более конкретными. И сами классы теперь сфокусированы на исполнение ровно одной функции.
Надо заметить, что такое разделение имеет смысл только если каждый из новых классов используется отдельными потребителем. Иначе, если потребитель <code>OrderService</code> был только один, таким разделением мы только увеличим количество связей в системе.
</p>
</aside>
</section>
<section id="slide-orgd972503">
<h2 id="orgd972503">Low coupling & High cohesion</h2>
<p>
Модули, которые следуют принципам слабой связанности и высокой сочетаемости, обладают следующими свойствами:
</p>
<ul>
<li>Изменения в одном модуле не влияют на остальные модули</li>
<li>Проще разбираться в коде модуля, без необходимости изучать остальные модули</li>
<li>Удобство в переиспользовании</li>
</ul>
<aside class="notes">
<p>
Coupling влияет на то, насколько просто нам поменять код, когда нам это нужно. Сколько мест нам нужно поменять и как будут эти изменения распространяться на остальную систему.
Сильно связанные модули не обладают гибкость, их сложно переиспользовать в других местах и как следствие плохо поддаются тестированию.
При изменении одного класса в сильно связанной программе часто необходимо внести изменения и в другие. В небольшой программе это не страшно, часто мы легко можем понять, что затронут наши изменения, шанс допустить ошибку невелик. Но с ростом приложения эти неявные взаимосвязи не всегда известны всем разработчикам и вероятность ошибки сильно возрастает.
</p>
<p>
Cohesion влияет на то, как быстро мы можем понять где нам нужно поменять код. Как только находим одно место, которое необходимо поменять есть высокая вероятность, что рядом будут и остальные места, которые нужно изменить.
Низкая сочетаемость означает, что код, который реализуют какую-то функцию или бизнес процесс в приложении размазан по всей кодовой базе. Из за этого тяжело понять, какой код относится к конкретному функционалу и приходится постоянно переключаться между модулями, для того чтобы построить в голове общую картину.
</p>
<p>
Чтобы определить, на сколько ваш код соответствует принципам низкой связанности и высокой сочетаемости можно задать себе вопросы из книги Программист прагматик. Когда вы сталкиваетесь с проблемой, оцените, насколько локален процесс ее устранения. Нужно изменить лишь один модуль, или изменения должны происходить по всей системе? Когда вы меняете что-либо, устраняются ли при этом все ошибки или происходит загадочное появление новых?
</p>
<p>
Часто, когда разработчик пытается реализовать рекомендации по низкому coupling, высокому cohesion, он прикладывает слишком много усилий к реализации первой рекомендации (низкий coupling) и полностью забывает о другой. Это приводит к ситуации, когда код действительно разделен (decoupled), но в то же время не имеет четкой направленности. Его части настолько отделены друг от друга, что становится трудно или даже невозможно понять их назначение. Эта ситуация называется деструктивной развязкой (destructive decoupling).
</p>
<p>
Coupling и Cohision всегда упоминают в паре, и это не случайно. На практике это две силы, которые противоречат друг другу. То есть, чтобы создать максимально слабосвязанную систему можно просто поместить весь код в один файл. Нет модулей - нет связей между ними - нет проблем. Но при этом такой код будет обладать слабой сочетаемостью, так как код внутри файла будет решать функционально разные проблемы.
С другой стороны, чтобы добиться максимального Cohesion нужно выделить каждую функцию в отдельный класс. Но в такой системе будет очень сильный Coupling между такими классами.
</p>
<p>
Собственно задача программиста при написании кода соблюдать баланс между этими двумя понятиями - связанность и сочетаемость.
</p>
</aside>
</section>
<section>
<blockquote>
<p>
Design is About Balancing Cohesion and Coupling (not blindly following principles) – Copeland
</p>
</blockquote>
</section>
</section>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/reveal.js/dist/reveal.js"></script>
<script src="https://cdn.jsdelivr.net/npm/reveal.js/plugin/notes/notes.js"></script>
<script src="https://cdn.jsdelivr.net/npm/reveal.js/plugin/highlight/highlight.js"></script>
<script>
// Full list of configuration options available here:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
controls: true,
progress: true,
history: false,
center: true,
slideNumber: false,
rollingLinks: false,
keyboard: true,
mouseWheel: false,
fragmentInURL: false,
hashOneBasedIndex: false,
pdfSeparateFragments: true,
overview: true,
transition: 'fade',
transitionSpeed: 'default',
// Plugins with reveal.js 4.x
plugins: [ RevealNotes, RevealHighlight ],
// Optional libraries used to extend reveal.js
dependencies: [
]
});
</script>
</body>
</html>