Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Domain Re-discovery Patterns for Legacy Code (v...

Richard
November 13, 2024
21

Domain Re-discovery Patterns for Legacy Code (v3) 🇬🇧 @BuildStuff 2024

In the world of software development, legacy code often poses significant challenges. Existing codebases, built years ago, may lack documentation and understanding of the original domain. This lack of knowledge can hinder effective maintenance, upgrades, and feature development. The process of rediscovering the domain of legacy code is invaluable for developers seeking to enhance and extend these systems.

In this talk we'll delve into various domain re-discovery patterns that help in identifying and reconstructing the domain model that the legacy code represents. These patterns go beyond merely deciphering the code's functionality; rather, they provide strategies to comprehend the underlying concepts, behaviors, and relationships in the domain.

Attendees can expect to gain practical insights, methodologies, and practical tips to tackle the challenges associated with legacy codebases. By embracing domain rediscovery patterns, developers can bring order and coherence to legacy systems, paving the way for future enhancements and system evolution.

Richard

November 13, 2024
Tweet

More Decks by Richard

Transcript

  1. 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
  2. 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
  3. Some code is not very evident Slide 4 by richargh.de

    from ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
  4. 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
  5. Where do you even start? Slide 6 by richargh.de from

    999+ Bugs Issues 999+ Smells 999+
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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 <<interface>> IsbnClient findByName(:String): Isbn <<interface>> Books getBy(:Isbn): Book? Put(:Book) Pattern: Strengthen Domain with Ports
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. Slide 31 by richargh.de from Characterized behavior Domain Clusters Code

    is testable „When will it be done“-Pressure
  29. 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?
  30. 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?
  31. 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. * ✗
  32. 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*
  33. 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
  34. 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
  35. 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
  36. Slide 39 by richargh.de from Characterized behavior Change here breaks

    there Domain Clusters Code is testable Quality Views
  37. 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
  38. 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
  39. 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.
  40. 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
  41. 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
  42. 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
  43. Slide 46 by richargh.de from Characterized behavior Tests become blockers

    Decoupled some Domain Clusters Code is testable Quality Views
  44. 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”, …)
  45. 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
  46. 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.} <module>/renting.integration.test.ts Pattern: Outside-in Tests via Dsl
  47. 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.} <module>/renting.unit.test.ts Pattern: Outside-in Tests via Dsl
  48. Slide 51 by richargh.de from Unit+Integration Tests Outside-in Tests Innovation

    jam Decoupled some Domain Clusters Code is testable Quality Views
  49. 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
  50. The domain says hello Slide 53 by richargh.de from Generated

    with: https://github.com/Richargh/code-tagcloud-py-sandbox Pattern: Code Tag Cloud
  51. 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
  52. 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
  53. 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 <firstname>.<lastname> at maibornwolff de Contact Ask me about these topics ☺ richargh.de/ rigross If you want help
  54. 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
  55. 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?
  56. 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
  57. 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)
  58. 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
  59. How do we refactor what we don‘t understand? 65 Slides

    by @arghrich Autumn: Product Baseline
  60. 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
  61. Why all the effort to re-discover? We could just start

    a-new! Slide 67 by richargh.de from
  62. 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
  63. 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
  64. 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
  65. 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%
  66. 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
  67. 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/
  68. 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
  69. 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
  70. 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
  71. 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
  72. 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
  73. The Domain gets top-level modules Slide 79 by richargh.de from

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? PatronId Book BookOnHold patron book dailysheet inventory
  74. inventory dailysheet Patron An ordered mess emerges Slide 80 by

    richargh.de from ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? PatronId Book BookOnHold Book
  75. 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
  76. 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
  77. 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
  78. 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).
  79. A brief coupling primer High Coupling Low Cohesion Low Coupling

    High Cohesion 89 Coupling Unknown Source
  80. 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/
  81. 4-axes of Connascence Strength Level How explicit Locality How close

    Degree Number of Impacts Volatiliy How much change
  82. 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/
  83. Metamorphic testing 93 Input Output testee x f(x) g(x) g(f(x))

    testee Derive via metamorphic relation Assert Check if relation holds