Slide 1

Slide 1 text

15.11.24 Richard Gross (he/him) Archaeology + Health Checks richargh.de/ speakerdeck.com/richargh @arghrich Hypermedia TestDSLs Domain Re-discovery Patterns for Legacy Code

Slide 2

Slide 2 text

Domain-Evident Code? Slide 2 by richargh.de from

Slide 3

Slide 3 text

Some domains are not very evident Slide 3 by richargh.de from Generated with: https://github.com/Richargh/code-tagcloud-py-sandbox Stringly or Strongly Typed? Pattern: Code Tag Cloud

Slide 4

Slide 4 text

Some code is not very evident Slide 4 by richargh.de from ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

Slide 5

Slide 5 text

Slide 5 by richargh.de from Fear of change Tests become blockers Innovation jam Change here breaks there Lack of domain Code not testable „When will it be done“-Pressure

Slide 6

Slide 6 text

Where do you even start? Slide 6 by richargh.de from 999+ Bugs Issues 999+ Smells 999+

Slide 7

Slide 7 text

Map your Code with CodeCharta1 buildings Slide 7 by richargh.de from Plug by Zaufishan Gource is a cool git visualizer https://gource.io/ CodeScene is a good Charta-alternative: https://codescene.com/ 1 CodeCharta is open-source https://maibornwolff.github.io/codecharta/ f.ex. Complexity f.ex. Number of authors Lines of Code SomeService.kt

Slide 8

Slide 8 text

Map your Code. Master your Legacy. Slide 8 by richargh.de from CodeCharta visualisation https://maibornwolff.github.io/codecharta/ Lines of Code Cycl. Complexity Churn (high) Pattern: Code Tag Cloud ModuleService Lot‘s of code. Many decisions. Lot‘s of change. But, intention-hiding name

Slide 9

Slide 9 text

Start at the Edge Slide 9 by richargh.de from ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Pattern Repos Controllers 3rd Party Services Intentions tend to be more obvious here ModuleService DataOpsController

Slide 10

Slide 10 text

Conditionals guide the way Slide 10 by richargh.de from 1. class DataOpsController { 2. 3. void handle(req, res){ 4. // … more code here 5. switch(req.action): 6. case ‘RNT’: 7. // … a lot of code here, then 8. res.send(b); 9. case ‘RTRN’: 10. // … a lot more code here 11. res.send(b); 12. case ‘CRT’: 13. // … something completely different 14. res.send(b); 15. case ‘EMT’: 16. // wait what?! 17. messaging().multicast(m); 18. } Pattern: Start at the Edge

Slide 11

Slide 11 text

Extracting all that code into new classes clears things up a bit Slide 11 by richargh.de from “AppleSauce” idea straight from https://www.digdeeproots.com/articles/on/naming-process/ 1. class DataOpsController { 2. 3. void handle(req, res){ 4. // … more code here 5. switch(req.action): 6. case ‘RNT’: 7. const appleSauce1 = AppleSauce1.handle(req, res) 8. res.send(appleSauce1); 9. case ‘RTRN’: 10. const appleSauce2 = AppleSauce2.handle(req, res) 11. res.send(appleSauce2); 12. case ‘CRT’: 13. const createdBookDto = CreateBook.handle(req, res) 14. res.send(createdBookDto); 15. case ‘EMT’: 16. const broadcastMessageDto = CreateBroadcast.handle(req) 17. messaging().multicast(broadcastMessageDto) 18. } Pattern: Start at the Edge Obvious nonsense names are ok But obvious things should be named

Slide 12

Slide 12 text

Make your controller humble1 Slide 12 by richargh.de from 1 https://martinfowler.com/bliki/HumbleObject.html 1. class AppleSauce1 { 2. // … 3. void handle(req, res){ 4. const isbn = http.get(“is.bn?name=${req.body.name}”) 5. const b = db(“SELECT * FROM Book WHERE isbn = $isbn”) 6. if(b.status == ‘RENTED’) 7. res.send(404) 8. 9. const rentedBook = b.copyWith({ 10. status: ‘RENTED’, 11. rentedUntil: Instant.of(req.body.until) 12. }) 13. 14. const user = db.userTable.getById(req.token.id) 15. if(!user.permits.contains(‘A38’)) 16. res.send(403) 17. 18. db.bookTable.save(rentedBook) 19. 20. // … a lot more code here 21.} 1. 2. 3. 4. Access External service 5. Raw Access Repository 6. Guard 7. 8. 9. 10. 11.Translate Dto -> Domain 12. 13. 14.Access Repository 15.Guard 16. 17. 18.Access Repository Friends don’t let friends inject SQL Pattern: Humble Object

Slide 13

Slide 13 text

Recognize the common abstractions Slide 13 by richargh.de from 1. class AppleSauce1 { 2. // … 3. void handle(req, res){ 4. const isbn = http.get(“is.bn?name=${req.body.name}”) 5. const b = db(“SELECT * FROM Book WHERE isbn = $isbn”) 6. if(b.status == ‘RENTED’) 7. res.send(404) 8. 9. const rentedBook = b.copyWith({ 10. status: ‘RENTED’, 11. rentedUntil: Instant.of(req.body.until) 12. }) 13. 14. const user = db.userTable.getById(req.token.id) 15. if(!user.permits.contains(‘A38’)) 16. res.send(403) 17. 18. db.bookTable.save(rentedBook) 19. 20. // … a lot more code here 21. } Presentation Domain Data Application

Slide 14

Slide 14 text

Don’t do this … without tests Slide 14 by richargh.de from 1. class AppleSauce1 { 2. // … 3. void handle(req, res){ 4. const rentedUntil = Instant.of(req.body.until) 5. 6. const user = db.userTable.getById(req.token.id) 7. if(!user.permits.contains(‘A38’)) 8. throw new PermissionDeniedException(user.id) 9. 10. const isbn = http.get(“is.bn?name=${req.body.name}”) 11. const b = db(“SELECT * FROM Book WHERE isbn = $isbn”) 12. if(b.status == ‘RENTED’) 13. throw new BookIsAlreadyRentedException(b.id) 14. 15. const rentedBook = b.copyWith({ 16. status: ‘RENTED’, 17. rentedUntil: rentedUntil 18. }) 19. 20. db.bookTable.save(rentedBook) 21. } Changing the execution order of stuff is a good way to get bugs Presentation Domain Data Application

Slide 15

Slide 15 text

But do do this … without tests but with IDE Slide 15 by richargh.de from 1. class AppleSauce1 { 2. void applesauce1(req, res){ 3. const isbn = isbnClient.findByName(req.body.name) 4. const b = books.getBy(isbn) 5. // [...] 6. const user = users.getById(req.token.id) 7. // [...] 8. books.put(rentedBook) 9. 10. // … a lot more code here 11. } Presentation Domain Data Application Pattern: Strengthen Domain with Ports This menu is your best friend

Slide 16

Slide 16 text

Strengthen domain with ports • Ports define what the domain needs from the outside world • They decouple from the actual implementation Slide 16 by richargh.de from <> IsbnClient findByName(:String): Isbn <> Books getBy(:Isbn): Book? Put(:Book) Pattern: Strengthen Domain with Ports

Slide 17

Slide 17 text

Make the class testable Slide 17 by richargh.de from 1. class AppleSauce1 { 2. 3. AppleSauce1( 4. IsbnClient isbnClient, 5. Users users, 6. Books books){ 7. // set the parameters 8. } 9. 10. void applesauce1(req, res){ 11. const isbn = isbnClient.findByName(req.body.name) 12. const b = books.getBy(isbn) 13. // [...] 14. const user = users.getById(req.token.id) 15. // [...] 16. books.put(rentedBook) 17. 18. // … a lot more code here 19. } Pattern: Configurable dependencies We can now configure these in our tests

Slide 18

Slide 18 text

We can split but not reorder without tests Slide 18 by richargh.de from DataOpsController Presentation Domain Data Application delegates to AppleSauce1 DataOpsController Humble Object Introduce Ports delegates to AppleSauce1 DataOpsController delegates to AppleSauce1 DataOpsController Configurable Ports

Slide 19

Slide 19 text

Can we test a system we do not understand? Slide 19 by richargh.de from

Slide 20

Slide 20 text

Characterize your classes Given enough inputs We will cover every line of our testee Slide 20 by richargh.de from See also https://approvaltests.com/ “AppleSauce” idea straight from https://www.digdeeproots.com/articles/on/naming-process/ AppleSauce1.Characterization.txt 1. class AppleSauce1 { 2. void handle(req, res){ 3. ~~~ 4. ~~~ 5. if(~~~) 6. ~~~ 7. 8. ~~~ 9. ~~~ 10. if(~~~) 11. ~~~ 12. 13. ~~~ 14.} 1. [foo, 42, true] => None, 2024-01-01 2. [bar, 12, false] => Almost, 2099-01-01 3. [bla, 0, null] => Finally, null Line covered Pattern

Slide 21

Slide 21 text

Characterization tests capture a snapshot of the system • We don’t know why • They’ll tell us when we refactor • They’ll stop us when we want to change behavior  Slide 21 by richargh.de from 1 https://martinfowler.com/books/refactoring.html Refactoring1: change structure without changing behavior

Slide 22

Slide 22 text

Inverse Object Mother Approach 1. Start application with empty database 2. Click through a UseCase 3. Analyse exceptions and errors • „App needs at least an object A with this field“ 4. Expand Domain Modell with your finding 5. Create required state in the DB with your model 6. Document finding as characterization test 7. Repeat Slide 22 by richargh.de from 1. // Required state, temporarily in main 2. // we’ll move this to test soon 3. void main() { 4. oneCharacterization(); 5. } 6. // characterizations have no concept of why 7. void displaysListOfBooksOnStart(){ 8. // needs a user 9. createUser(); 10. // needs at least one author 11. var author = createAuthor(); 12. // needs at least one book 13. var book = createBook(author); 14. // … needs xyz as well 15.} Pattern

Slide 23

Slide 23 text

Slide 23 by richargh.de from Characterized behavior Lack of domain Code is testable

Slide 24

Slide 24 text

Small changes, big knowledge boost Slide 24 by richargh.de from 1 uses the checker framework https://checkerframework.org/ Ids Units of measure 1. var rawUserId = 123; 2. var userId = UserId.of(123); 3. var bookId = BookId.of(123); 4. 5. // allowed, but is a bug 6. Book book = RawGetBook(rawUserId); 7. 8. // produces a design-time error 9. Book book = GetBook(bookId); 10. 11. 12.void RawGetBook(int id){ /* … */ } 13.void GetBook(BookId id){ /* … */ } 1. Meters meters = Meters.Of(5); 2. Seconds seconds = Seconds.Of(2); 3. Money money = Money.Of(5, Currency.Euros); 4. 5. // allowed 6. Speed speed = meters.Per(seconds); 7. 8. // produces a design-time error 9. var foo = meters + seconds; Pattern: Strongly-typed Primitives

Slide 25

Slide 25 text

You probably have some form of grouping Slide 25 by richargh.de from “AppleSauce” idea straight from https://www.digdeeproots.com/articles/on/naming-process/ AppleSauce1 DataOpsController XyzController AppleSauce2 CreateBook MagicSauce1 MagicSauce2 AppleSauce? ModuleService Books? PearSauce? AbstractBase Service

Slide 26

Slide 26 text

Entity Ownership 1. grep Reads: SELECT, JOIN 2. grep Writes: INSERT, UPDATE, DELETE 3. Table or plot for each entity which components reads an entity and which writes Slide 26 by richargh.de from Pattern

Slide 27

Slide 27 text

Who reads and writes book? Slide 27 by richargh.de from Read Scattered writes • Is entity contract protected everywhere? • Is entity contract in sync everywhere? (pre/post conditions, invariants) Scattered reads • Does everyone really need the entity? • Does everyone need the same fields? Write Pattern: Entity Ownership

Slide 28

Slide 28 text

Bound the Entity Ownership Only one write location • Don’t write the entity if you don’t own it • If you have to write, delegate to owner • The owner knows what a valid entity is Read • If scattered reads have little field overlap, consider splitting entity • Get feedback on domain names of splits • See if split has a different owner • Keep it in sync via events Slide 28 by richargh.de from

Slide 29

Slide 29 text

Define your understanding as code Slide 29 by richargh.de from Example uses ArchUnit https://www.archunit.org/ 1. @ArchTest 2. static final ArchRule no_classes_should_depend_on_service = 3. freeze( // accept existing violations 4. noClasses() 5. .should() 6. .dependOnClassesThat() 7. .resideInAPackage("..service..")); 8. @ArchTest 9. static final ArchRule services_should_not_access_controllers = 10. noClasses() // only green without violations 11. .that().resideInAPackage("..service..") 12. .should().accessClassesThat().resideInAPackage("..controller.."); Pattern: North-Star Architecture

Slide 30

Slide 30 text

Map your frozen architecture violations. Slide 30 by richargh.de from CodeCharta visualisation https://maibornwolff.github.io/codecharta/ Lines of Code Arc. Violations Arc. Violations (high) Pattern: North-Star Architecture

Slide 31

Slide 31 text

Slide 31 by richargh.de from Characterized behavior Domain Clusters Code is testable „When will it be done“-Pressure

Slide 32

Slide 32 text

Legacy means less predictability for when will it be done* Slide 32 by richargh.de from * When you don’t have legacy code, your predictability mainly depends on how small your make your work items 10% 30% 50% 70% 90% Certainity of estimate Mo Tu We Th Fr 1 2 3 6 7 8 9 10 13 14 15 16 17 20 21 22 23 24 27 28 29 30 1 4 5 6 7 8 When will it be done?

Slide 33

Slide 33 text

Legacy means less predictability for when will it be done* Slide 33 by richargh.de from * When you don’t have legacy code, your predictability mainly depends on how small your make your work items 10% 30% 50% 70% 90% Certainity of estimate Mo Tu We Th Fr 1 2 3 6 7 8 9 10 13 14 15 16 17 20 21 22 23 24 27 28 29 30 1 4 5 6 7 8 When will it be done?

Slide 34

Slide 34 text

Colorize based on predictability Slide 34 by richargh.de from * Define appropriate checkboxes as a team. This is just a suggestion Increasing predictability Patron Component Enforced component bounds (f.ex. Arch-Unit tests) ✗ Characterization tests coverage 100% ✓ Domain Unit tests mutation coverage +80% ✗ No obsolete or critically vulnerable depedendencies used ✗ Presentation-Data-Domain Layering ✗ ✓ Yes ✗ No ∅ Does not apply Component testable ✓ Behavior Structure Pattern: Quality Views Service-Level Agreements Etc. * ✗

Slide 35

Slide 35 text

Colorize based on predictability Slide 35 by richargh.de from * Define appropriate checkboxes as a team. This is just a suggestion Increasing predictability Component ✗ ✓ ✗ ✗ ✗ ✓ Yes ✗ No ∅ Does not apply ✓ Behavior Structure Pattern: Quality Views Service-Level Agreements ✗ Patron 2/10*

Slide 36

Slide 36 text

Communicate High-Level-View Slide 36 by richargh.de from Quality Views Based-on https://blog.colinbreck.com/using-quality-views-to-communicate-software-quality-and-evolution/ Increasing predictability Better Worse Patron Book Inventory Component Locked Transforming for future features Focus Slight quality dip since last communication No changes planned or wanted Pattern: Quality Views

Slide 37

Slide 37 text

Capability-View Slide 37 by richargh.de from Quality Views Based-on https://blog.colinbreck.com/using-quality-views-to-communicate-software-quality-and-evolution/ Increasing predictability Better Worse Patron Book Inventory Component Capability Locked Feature-planning often requires capability details Focus search quote book search Pattern: Quality Views

Slide 38

Slide 38 text

By adressing these Slide 38 by richargh.de from Behavior Structure We’ll unearth Domain concepts ✓ Behavior documentation (tests ☺) ✓ Service-Level Agreements 10% 30% 50% 70% 90% Certainity of estimate And fix this

Slide 39

Slide 39 text

Slide 39 by richargh.de from Characterized behavior Change here breaks there Domain Clusters Code is testable Quality Views

Slide 40

Slide 40 text

Map your Temporal Coupling Slide 40 by richargh.de from 1 from the book https://pragprog.com/titles/atcrime/your-code-as-a-crime-scene CodeCharta visualisation https://maibornwolff.github.io/codecharta/ Lines of Code Cycl. Complexity Number of authors (high) Ingoing Temporal Coupling RentService RentService Notice that no compile-time relation exists between the temporally coupled files. Pattern: Temporal Coupling1

Slide 41

Slide 41 text

C is always commited together with … Slide 41 by richargh.de from 1 from the book https://pragprog.com/titles/atcrime/your-code-as-a-crime-scene B C A Temporal Coupling Static Coupling Both are forms of Connascence Patron Book Dailysheet Inventory D E Pattern: Temporal Coupling1

Slide 42

Slide 42 text

Slide 42 by richargh.de from Connascence by Meilir Page-Jones, “Fundamentals of Object-Oriented Design in Uml” 2 elements A,B are connascent if there is at least 1 possible change to A requires a change to B in order to maintain overall correctness.

Slide 43

Slide 43 text

Connascence of Slide 43 by richargh.de from Connascence: https://www.maibornwolff.de/en/know-how/connascence-rules-good-software-design/ • Name: variable, method, SQL Table • Type: int, String, Money, Person • Meaning: what is true,‘YES‘,null • And 6 more, including Execution Order Easy Hard on your brain Good Bad Refactor this way Hidden domain Explicit Domain Expensive Change Cheap

Slide 44

Slide 44 text

Connascence guides refactoring Slide 44 by richargh.de from Connascence: https://www.maibornwolff.de/en/know-how/connascence-rules-good-software-design/ Connascence of Name 1. // A) 2. enum MovieType { } 3. // B) 4. sealed interface Movie permits RegularMovie { } 3. // C) 4. interface Movie { 5. int amount(){ … } 6. } 7. // D) 8. // appropriate solution is a team effort 1. // A) 2. static int OLD_PEOPLE_PENALTY = 25; 3. // B) 4. // appropriate solution is a team effort ☺ Connascence of Meaning

Slide 45

Slide 45 text

Let Connascence guide the decoupling Slide 45 by richargh.de from Strong of Connascence Weak of Connascence Reduce Strength Increase Locality New Domain Concept Lock Element Locked Domain Concept

Slide 46

Slide 46 text

Slide 46 by richargh.de from Characterized behavior Tests become blockers Decoupled some Domain Clusters Code is testable Quality Views

Slide 47

Slide 47 text

Tests can cement structure and block progress Slide 47 by richargh.de from The redundant initialization in n tests cements the design of the type class Book { … } Test 1 new Book(“1”, “Abc”, …) Test n new Book(“n”, “xyz”, …)

Slide 48

Slide 48 text

Outside-in Tests via Dsl Context • Keep tests structure-insensitive when you don’t know what your future structure will look like • Be able to convert integration tests to unit tests after remodelling Approach • Use an abstraction for the test setup. Don‘t let tests directly … • create entities • put entities into db • Stub out external systems • Write tests outside-in Slide 48 by richargh.de from See also Java Aktuell 4/24 and https://richargh.de/posts/Structure-Cementing-Tests-1 Pattern

Slide 49

Slide 49 text

Start with an integration test Slide 49 by richargh.de from See also Java Aktuell 5/24 and https://richargh.de/posts/Structure-Cementing-Tests-1 1. // create the low-level test-DSL 2. // small test, all secondary ports are now stubs or fakes, they never connect to the real world 3. const { a, secondaryPorts } = integrationTest().withoutState().buildDsl(); 4. 5. test(‘should be able to rent book’, () => { 6. // GIVEN 7. const book = a.book(); // I need a book, don’t care which 8. const { user } = a.userBundle(it => it.hasPermission(“CAN_RENT_BOOK”); // a user, don’t care who 9. 10. await a.saveTo(secondaryPorts); // store book and user entities in repositories 11. 12. const testee = configureRentingComponent(floor); // configure dependencies of component 13. // WHEN 14. const result = testee.rentBook(book, user); 15. // THEN 16. expect(result.isRented).toBeTrue(); 17.} /renting.integration.test.ts Pattern: Outside-in Tests via Dsl

Slide 50

Slide 50 text

Go unit with one change, once all db logic is in domain Slide 50 by richargh.de from See also Java Aktuell 6/24 and https://richargh.de/posts/Structure-Cementing-Tests-1 1. // create the low-level test-DSL 2. // small test, all secondary ports are now stubs or fakes, they never connect to the real world 3. const { a, secondaryPorts } = unitTest().withoutState().buildDsl(); 4. 5. test(‘should be able to rent book’, () => { 6. // GIVEN 7. const book = a.book(); // I need a book, don’t care which 8. const { user } = a.userBundle(it => it.hasPermission(“CAN_RENT_BOOK”); // a user, don’t care who 9. 10. await a.saveTo(secondaryPorts); // store book and user entities in repositories 11. 12. const testee = configureRentingComponent(floor); // configure dependencies of component 13. // WHEN 14. const result = testee.rentBook(book, user); 15. // THEN 16. expect(result.isRented).toBeTrue(); 17.} /renting.unit.test.ts Pattern: Outside-in Tests via Dsl

Slide 51

Slide 51 text

Slide 51 by richargh.de from Unit+Integration Tests Outside-in Tests Innovation jam Decoupled some Domain Clusters Code is testable Quality Views

Slide 52

Slide 52 text

We now have the safety to reach our goal Slide 52 by richargh.de from Presentation Domain Data Application delegates to AppleSauce1 DataOpsController BookController CreateBook RentBook handle handle Configurable Dependencies Configurable Dependencies xyzController DoXyz inform Configurable Dependencies

Slide 53

Slide 53 text

The domain says hello Slide 53 by richargh.de from Generated with: https://github.com/Richargh/code-tagcloud-py-sandbox Pattern: Code Tag Cloud

Slide 54

Slide 54 text

So does predictability Slide 54 by richargh.de from Increasing predictability Better Worse Patron Book Inventory Component Capability Locked Focus search quote book search M T W T F 1 2 3 6 7 8 9 10 13 14 15 16 17 20 21 22 23 24 27 28 29 30 1 4 5 6 7 8 M T W T F 1 2 3 6 7 8 9 10 13 14 15 16 17 20 21 22 23 24 27 28 29 30 1 4 5 6 7 8 M T W T F 1 2 3 6 7 8 9 10 13 14 15 16 17 20 21 22 23 24 27 28 29 30 1 4 5 6 7 8 Pattern: Quality Views

Slide 55

Slide 55 text

We could talk even more about patterns Slide 55 by richargh.de from Map Temporal Coupling Passive Code Tag Cloud Passive Map Coordination Bottlenecks Passive Package by Component Active Map Knowledge Silos Passive Quality Views Communication Map Churn Passive Inverse Object Mother Active Entity Ownership Passive Outside-in Tests via DSL Active

Slide 56

Slide 56 text

richargh.de Slide 56 by richargh.de from In case you want some help

Slide 57

Slide 57 text

richargh.de Slide 57 by richargh.de from Thank you @arghrich Richard Gross (he/him) IT Archaeology + Health Checks Hypermedia TestDSLs Works for maibornwolff.de/ Slides, Code, Videos Contact me . at maibornwolff de Contact Ask me about these topics ☺ richargh.de/ rigross If you want help

Slide 58

Slide 58 text

Backup

Slide 59

Slide 59 text

Our domain models can also give feedback to the domain Slide 59 by richargh.de from

Slide 60

Slide 60 text

Passive Pattern: Map Coordination Bottlenecks Context • Code elements that everyone changes usually require extensive coordination to avoid conflicts. Approach 1. In the code-map, mark complex elements where most of the team have made recent changes. Slide 60 by richargh.de from

Slide 61

Slide 61 text

Passive Pattern: Map Coordination Bottlenecks Slide 61 by richargh.de from CodeCharta visualisation https://maibornwolff.github.io/codecharta/ Lines of Code DataMocks.kt Lot‘s of code, but no decisions. Probably fine. Cycl. Complexity Number of authors (high) RentingService.kt Lot‘s of code, many decisions and 20 authors. Why?

Slide 62

Slide 62 text

Passive Pattern: Map Knowledge Silos Context • Code elements that are only changed by few authors are likely only understood by these authors. • If the elements are complex and only have one author, we have a business risk as well. Approach 1. In the code-map, mark complex elements that have only 1 or 2 authors. 2. Hightlight elements where the author is about to leave or has left. Slide 62 by richargh.de from See also https://codescene.com/knowledge-distribution

Slide 63

Slide 63 text

Passive Pattern: Map Knowledge Silos Slide 63 by richargh.de from CodeCharta visualisation https://maibornwolff.github.io/codecharta/ Lines of Code Librarian.kt Medium code, medium complex, but only one person knows about it Cog. Complexity Number of authors (low)

Slide 64

Slide 64 text

Active Pattern: Knowledge Sharing Context • Mitigate the business risk of knowledge silos Caveat • Having everyone know everything is time-consuming and wasteful due to forgetfulness Approach • “Owner” delegates changes and reviews • Pair/Mob programming • Dev Talk Walkthrough • Simple code • Specification by (test) example Slide 64 by richargh.de from

Slide 65

Slide 65 text

How do we refactor what we don‘t understand? 65 Slides by @arghrich Autumn: Product Baseline

Slide 66

Slide 66 text

66 Slides by @arghrich Autumn: Product Baseline Emily Bache @emilybache „Nopefactoring“ The No-thinking refactoring“ Advanced Testing & Refactoring Techniques • Lift-up conditional • Split to Classes Llewellyn Falco @LlewellynFalco Cutting Code Quickly

Slide 67

Slide 67 text

Why all the effort to re-discover? We could just start a-new! Slide 67 by richargh.de from

Slide 68

Slide 68 text

The True Cost of Feature-based Rewrites Slide 68 by richargh.de from Original Article by Doug Bradbury The True Cost of Rewrites Features Releases over time Catch Up Missing Features Sub-Par Parity Enhancements Planned Rewrite Actual Features Old App Adoption

Slide 69

Slide 69 text

The cost of the rewrite depends on your approach Feature-based rewrite • Goal = Feature + Feature + Feature • Incrementally build feature after feature • Release when all are done Outcome-based rewrite • Goal = achieve an outcome • Write the minimal thing to achieve outcome • Iterate 69

Slide 70

Slide 70 text

Generate and view a CodeCharta map 70 npm install -g codecharta-analysis git clone [email protected]:MaibornWolff/codecharta.git ccsh sonarimport https://sonarcloud.io -o petclinic.code.cc.json ccsh gitlogparser repo-scan --repo-path=spring-petclinic/ -o petclinic.git.cc.json ccsh merge petclinic.git.cc.json.gz petclinic.code.cc.json.gz -o petclinic.cc.json → Open petclinic.cc.json.gz in https://maibornwolff.github.io/codecharta/visualization/app/index.html The official docs: https://maibornwolff.github.io/codecharta

Slide 71

Slide 71 text

Communication Pattern: Quality Views Parking-Lot View 71 Increasing changeability Better Worse Pricing Component Capability Locked Focus 08 2024 +- 25% RG Component Champion 23% {10}+[25]+25% Component Name {Scoped} + [Unscoped] Transformations Completion of Scoped Transformation Expected Completion Month + Uncertainity%

Slide 72

Slide 72 text

Invoicing Communication Pattern: Quality Views Even more detail 72 Increasing changeability Better Worse Pricing Renting Locked Feature-planning often requires more details Focus Component Capability FE BE search quote book bill liquidate

Slide 73

Slide 73 text

Pattern: Complexity1 Invest Context • Cyclomatic complexity1 counts places where the control flow branches (if, for, catch, …). • A lot of complexity is an indicator that domain decisions are being made. Approach 1. In the code-map mark the places with a lot of complexity Caveat • Cyclomatic complexity penalizes switch cases heavily and ignores indendation2,3 73 1 McCabe‘s cyclomatic complexity (MCC) counts branches in control flow (if, for, while, catch) 2 Alternative: Cognitive Complexity https://www.sonarsource.com/resources/cognitive-complexity/ 3 Alternative: Indendation based „Bumby Road“ smell https://codescene.com/engineering-blog/bumpy-road-code-complexity-in-context/

Slide 74

Slide 74 text

Pattern: Complexity Invest 74 Lines of Code Cycl. Complexity Cycl. Complexity (high) CodeCharta Code visualized by CodeCharta https://maibornwolff.github.io/codecharta/ codemap ribbonbar searchPanel store datamocks.ts loadInitialFile.service.ts „Interesting, why is that so complex“? nodeDecorator.ts „Node from the tag cloud, what does it do?“ viewCube.component.ts „Complex but does not show up in tag cloud, why?“ customConfigs

Slide 75

Slide 75 text

Active Pattern: Complexity Limit • Remove indentation with guard clauses • switch(anEnum) { case “A”: doThingA() } → polymorphic dispatch anABCobj.doThing(); • Replace flag argument1 with specific methods • Separate presentation from domain from data2 • Finally group things that only interact with each other and extract as new type • You now have new domain concepts to name 75 1 Flag arguments https://martinfowler.com/bliki/FlagArgument.html 2 Presentation Domain Data Layering https://martinfowler.com/bliki/PresentationDomainDataLayering.html

Slide 76

Slide 76 text

Anti-Pattern: Package by Layer1,2 Slide 76 by richargh.de from 1 Presentation-Domain-Data Layering: https://martinfowler.com/bliki/PresentationDomainDataLayering.html 2 Simon Brown has a good explanation as well: https://dzone.com/articles/package-component-and services repositories controllers utils models Controller A Controller B Presentation Layer Service A Service B Domain Layer Repository A Repository B Data Layer It‘s all about the frameworks Models intermixed with unrelated models. Utils are a smell

Slide 77

Slide 77 text

Active Pattern: Package by Component1,2 Context • Components mean: study & (heavily) change one thing at a time • Group together what fires together: • 1 feature, 1 commit, 1 component • Top-level components communicate domain • service/, models/, repositories/ vs • book/, inventory/, dailysheet/ Approach 1. Move interacting elements closer to each other (with IDE) 2. Start with the controller and group what it needs 3. Layering is only the secondary organisation mechanism Caveat • You can’t get it right the first time. A lot of spaghetti- dependencies will still exist for now Slide 77 by richargh.de from 1 Presentation-Domain-Data Layering: https://martinfowler.com/bliki/PresentationDomainDataLayering.html 2 Simon Brown has a good explanation as well: https://dzone.com/articles/package-component-and

Slide 78

Slide 78 text

commons Active Pattern: Package by Component1,2 Slide 78 by richargh.de from 1 Presentation-Domain-Data Layering: https://martinfowler.com/bliki/PresentationDomainDataLayering.html 2 Simon Brown has a good explanation as well: https://dzone.com/articles/package-component-and Controller A Controller B Presentation Layer Service A Service B Domain Layer Repository A Repository B Data Layer patron book dailysheet inventory

Slide 79

Slide 79 text

The Domain gets top-level modules Slide 79 by richargh.de from ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? PatronId Book BookOnHold patron book dailysheet inventory

Slide 80

Slide 80 text

inventory dailysheet Patron An ordered mess emerges Slide 80 by richargh.de from ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? PatronId Book BookOnHold Book

Slide 81

Slide 81 text

Communication Pattern: Quality Views Colorize based on changeability Slide 81 by richargh.de from Increasing changeability Patron Component Explicit Outside Api (Explicit Schema and Dtos) ∅ Enforced component bounds (f.ex. Arch-Unit tests) ✗ Characterization tests line coverage +90% ✓ Unit+integration tests line coverage +90% ✗ Key SLAs tested ✗ Presentation-Data-Domain Layering ✗ Code in good shape (low smells/good IOSP ratio) ✗ ✓ Yes ✗ No ∅ Does not apply Component testable (interact with outside world via ports) ✓ Unit test domain mutation coverage +90% ✗ Behavior Structure No obsolete or critically vulnerable depedendencies ✗ Built-in Logs, Metrics, Alerts ✗ 2/11 Tests over 20% Tests over 40% Tests over 60% Fitness functions No Arch Violations

Slide 82

Slide 82 text

The Dirty (but useful) Patterns 82

Slide 83

Slide 83 text

Some Modernizations required dirtier patterns than others 83

Slide 84

Slide 84 text

Passive Pattern: Activity Logging Context • Know which code parts are reached often and potentially critical • Know which code parts are not reached at all and are potentially obsolete Approach • Identify system entry points & deep interna, then log there • Alt: Prometheus Counter • Count in production Caveat • Some things are cyclical yearly/monthly (reports) 84

Slide 85

Slide 85 text

Active Pattern: Legacy Toggle Context • Know if a feature really is obsolete and deletable Approach • Add a UI toggle, count if activated (soft) • Deactivate in backend via env variable, reactivate env if someone complains (hard) • Increasing Thread.sleep before answer (evil) • Return static result, see if someone complains (rockstar) Caveat • Some things are cyclical (reports) • People still might not complain 85

Slide 86

Slide 86 text

Our highest priority is to satisfy the customer by not changing what doesn’t need changing. 86 The second principle of the legacy software manifesto (if one is ever written).

Slide 87

Slide 87 text

A brief coupling primer High Coupling Low Cohesion Low Coupling High Cohesion 89 Coupling Unknown Source

Slide 88

Slide 88 text

Connascence Guides Refactoring • Name: variable, method, SQL Table • Type: int, String, Money, Person • Meaning: what is true,‘YES‘,null,love • Position: order of value • Algorithm: encoding, SPA vs Server • Execution (order): one before other • Timing: doFoo() in 500ms | doBar() in 400ms • Value: constraints on value, invariants • Identity: reference same entity Easy Hard on your brain Good Bad Really Bad Refactor this way Connascence: https://www.maibornwolff.de/know-how/connascence-regeln-fuer-gutes-software-design/

Slide 89

Slide 89 text

4-axes of Connascence Strength Level How explicit Locality How close Degree Number of Impacts Volatiliy How much change

Slide 90

Slide 90 text

The 4½ types of testing 92 Oracle-based testing Property-based testing1 Characterization testing2,3 Metamorphic testing4 Captures intended behavior Captures observed behavior Captures intended properties Random inputs assert one property of all outputs Captures intended metamorphic relations One Test Specific input assert actual matches expected One Test (often) Generated inputs assert actual matches snapshot Many Tests One source, Derived inputs assert outputs keep relation to source One Test The remaining ½ is the mutation which you can use to test your tests. Mutate code, run test, see if enough tests break. 1 see jqwik https://jqwik.net/ 2 see also „Golden Master“ https://en.wikipedia.org/wiki/Characterization_test 3 Alternative name, „Approval Tests“ including test framework https://approvaltests.com 4 see https://www.hillelwayne.com/post/metamorphic-testing/

Slide 91

Slide 91 text

Metamorphic testing 93 Input Output testee x f(x) g(x) g(f(x)) testee Derive via metamorphic relation Assert Check if relation holds