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

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

Avatar for Richard Richard
February 12, 2026

Domain Re-discovery Patterns for Legacy Code (v3.2) 🇬🇧 @OOP 2026

Legacy code projects struggle before coding even begins. Which features are implemented, where they are located, and at what maturity level, is often unclear. In short, a gap exists between the business domain and what is implemented in code.

In green-field projects we use Domain-Driven Design tools and patterns, so the gap does not happen. An additional set of patterns is needed when we start out with legacy code though.

In this talk we'll explore the core patterns to rediscover the domain. These patterns go beyond merely deciphering the code's functionality. They provide strategies to comprehend the underlying concepts, behaviors, and relationships in the domain.

Avatar for Richard

Richard

February 12, 2026
Tweet

More Decks by Richard

Other Decks in Programming

Transcript

  1. 13.02.26 Richard Gross (he/him) Archaeology + Health Checks richargh.de/ speakerdeck.com/richargh

    richargh.de Hypermedia TestDSLs Domain Re-discovery Patterns for Legacy Code
  2. Some domains are not very evident Slide 3 CC BY-SA

    richargh.de Generated with: https://github.com/Richargh/code-tagcloud-py-sandbox Stringly or Strongly Typed? Pattern: Code Tag Cloud
  3. Fear of change Code not testable Innovation jam Tests become

    blockers Change here breaks there Lack of domain „When will it be done“-Pressure Slide 5 CC BY-SA richargh.de
  4. Where do you even start? 999+ Bugs Security Issues 999+

    Smells 999+ Slide 6 CC BY-SA richargh.de
  5. Ask the AITM Gobble up stuff Get a report *

    Reads typically use Haiku to figure out if the content is safe. ** Ctrl+O in Claude and verified via mitmproxy Slide 8 CC BY-SA richargh.de Subagent Explore() 1. Grep(TODO|FIXME|HACK|XXX) 2. Grep(password|secret|token|api_key|apikey) 3. Grep(eval|exec|dangerouslySetInnerHTML) 4. Grep(SELECT.*FROM.*WHERE) 5. Grep(componentWillMount|componentWillReceivePro ps) 6. Grep(race condition|deadlock|mutex|RwMutex) 7. Grep(CVE|vuln) 8. Grep(console.log|error) 9. Bash(npm audit) 10. Bash(npm run test) 11. LineCount(*.ts) 12. LineCount(*.spec.ts) 13. Read(all package.json) 14. Read(all *.md) 15. Read(jest|webpack.config.*) 16. Read(largest files) ## Critical issues 1. MASSIVE TECHNICAL DEBT FROM TODO/FIXME MARKERS 2. SQL INJECTION VULNERABILITIES RISK 3. EXCESSIVE USE OF DANGEROUS REACT PATTERNS 4. DEPRECATED REACT LIFECYCLE METHODS 5. MASSIVE COMPONENT SIZE AND COMPLEXITY 6. INCOMPLETE TEST COVERAGE ## RECOMMENDATIONS ### Immediate (0-30 days) 1. Security Audit SQL Queries 2. Remove console.log statements 3. Patch bootstrap 4. Audit all dangerouslySetInnerHtml
  6. Reliability? Repeatability? * Same prompt three times, /clear after each

    result Slide 9 CC BY-SA richargh.de Get a report Get another report ## Critical issues 1. MASSIVE TECHNICAL DEBT FROM TODO/FIXME MARKERS 2. SQL INJECTION VULNERABILITIES RISK 3. EXCESSIVE USE OF DANGEROUS REACT PATTERNS 4. DEPRECATED REACT LIFECYCLE METHODS 5. MASSIVE COMPONENT SIZE AND COMPLEXITY 6. INCOMPLETE TEST COVERAGE ## RECOMMENDATIONS ### Immediate (0-30 days) 1. Security Audit SQL Queries 2. Remove console.log statements 3. Patch bootstrap 4. Audit all dangerouslySetInnerHtml CRITICAL - Fix Immediately [1-2 weeks] 1. Hardcoded Sentry DSN Exposed 2. SQL Injection Vulnerabilities 3. Weak Cryptography (SHA-1, MD5) HIGH - Address Soon (1-2 months) 1. Monolithic App Layer (130K+ lines) 2. Technical Debt Backlog (TODO/FIXME) 3. Type Safety Issues (2,257 instances Total effort estimate: 715-1,080 hours Logging not mentioned Patching not mentioned Get a third report CRITICAL - Fix Immediately 1. XSS Vulnerability Risk 2. Insecure Cryptographic Hash (SHA-1) 3. Massive File Sizes HIGH - Fix Within Sprint 1. localStorage Security Issues 2. 1,606 TODO/FIXME Comments 3. Excessive any Type Usage Recommended Priority Order Week 1-2 (Security Sprint): 1. Fix XSS vulnerabilities 2. Replace SHA-1 with SHA-256 3. Implement CSP headers 4. Secure localStorage or migrate to HttpOnly cookies SQL not mentioned
  7. Map your Code with CodeCharta1 buildings 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 10 CC BY-SA richargh.de
  8. Map your Code. Master your Legacy. CodeCharta visualisation https://maibornwolff.github.io/codecharta/ Lines

    of Code Cycl. Complexity Churn (high) Pattern: Map your Code ModuleService Lot‘s of code. Many decisions. Lot‘s of change. And, intention-hiding name Slide 11 CC BY-SA richargh.de Where is it used?
  9. Start at the Edge Slide 12 CC BY-SA richargh.de Pattern

    Controllers DataOpsController ModuleService Repos Books 3rd Party Services
  10. Conditionals guide the way 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 13 CC BY-SA richargh.de
  11. Extracting all that code into new classes clears things up

    a bit “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 domain should be named Slide 14 CC BY-SA richargh.de
  12. Recognize the common abstractions 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.} Slide 15 CC BY-SA richargh.de
  13. Recognize the common abstractions 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.} Infrastructure Slide 16 CC BY-SA richargh.de
  14. Recognize the common abstractions 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.} Domain Infrastructure Slide 17 CC BY-SA richargh.de
  15. Recognize the common abstractions 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 Infrastructure Slide 18 CC BY-SA richargh.de
  16. Recognize the common layers 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 Infrastructure Application Slide 19 CC BY-SA richargh.de
  17. Realize the benefit of early guards… 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 Infrastructure Application Slide 20 CC BY-SA richargh.de
  18. But don’t just reorder without tests 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. res.send(403) 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 Infrastructure Application Slide 21 CC BY-SA richargh.de
  19. Do realize what the infrastructure is about 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 Infrastructure Application Slide 22 CC BY-SA richargh.de
  20. Then make it about the domain: without tests but with

    your IDE 1. class AppleSauce1 { 2. // … 3. void handle(req, res){ 4. const isbn = isbnClient.findByName(req.body.name) 5. const b = books.getBy(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 = users.getById(req.token.id) 15. if(!user.permits.contains(‘A38’)) 16. res.send(403) 17. 18. books.put(rentedBook) 19. 20. // … a lot more code here 21.} Presentation Domain Infrastructure Application Slide 23 CC BY-SA richargh.de Pattern: Strengthen Domain with Ports This menu is your best friend
  21. Strengthen domain with ports • Ports define what the domain

    needs from the outside world • They decouple from the actual implementation <<interface>> IsbnClient findByName(:String): Isbn <<interface>> Books getBy(:Isbn): Book? Put(:Book) Pattern: Strengthen Domain with Ports Slide 24 CC BY-SA richargh.de
  22. Make the class testable 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 25 CC BY-SA richargh.de
  23. We can split but not reorder without tests DataOpsController Presentation

    Domain Infrastructure Application delegates to AppleSauce1 DataOpsController Extract Actions Introduce Ports delegates to AppleSauce1 DataOpsController delegates to AppleSauce1 DataOpsController Configurable Ports Slide 26 CC BY-SA richargh.de
  24. Characterize your classes Given enough inputs We will cover every

    line of our testee 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 28 CC BY-SA richargh.de
  25. Characterization tests capture a snapshot of the system • They

    don’t tell us why it does what it does • They’ll tell us when we refactor • They’ll stop us when we want to change behavior  1 https://martinfowler.com/books/refactoring.html Refactoring1: change structure without changing behavior Slide 29 CC BY-SA richargh.de
  26. Small changes, big knowledge boost 1 uses the checker framework

    https://checkerframework.org/ Ids Units of measure 1. 2. var userId = UserId.of(123); 3. var bookId = BookId.of(789); 4. 5. // allowed 6. Book book = getBook(bookId); 7. 8. // produces a design-time error 9. Book book = getBook(userId); 1. Meters meters = Meters.Of(5); 2. Seconds seconds = Seconds.Of(2); 3. Money money = Money.Of(5, EUR); 4. 5. // allowed 6. Speed speed = meters.Per(seconds); 7. 8. // produces a design-time error 9. var foo = meters.plus(seconds); Pattern: Strongly-typed Primitives Slide 31 CC BY-SA richargh.de 1. # 1 solo field 2. record Isbn(String raw) 3. record HoldDuration() 4. # 2+ that appear together 5. record Cancellation() Domain Concepts
  27. Chunk your code into components (outliers where they’re used the

    most) “AppleSauce” idea straight from https://www.digdeeproots.com/articles/on/naming-process/ AppleSauce1 DataOpsController XyzController AppleSauce2 CreateBook MagicSauce TruffleSauce core/AppleSauce? abc supporting/Books? generic/PearSauce? def Slide 32 CC BY-SA richargh.de Pattern
  28. Inverse Object Mother 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 33 CC BY-SA richargh.de
  29. 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 Pattern Slide 34 CC BY-SA richargh.de
  30. Who reads and writes book? 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 35 CC BY-SA richargh.de
  31. 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 36 CC BY-SA richargh.de Pattern: Entity Ownership
  32. Define your understanding as code 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. .that().resideInAPackage("..common..") 6. .should().accessClassesThat().resideInAPackage("..patron..") 7. ); Pattern: North-Star Architecture Slide 37 CC BY-SA richargh.de
  33. Map your frozen architecture violations. CodeCharta visualisation https://maibornwolff.github.io/codecharta/ Lines of

    Code Arc. Violations Arc. Violations (high) Pattern: North-Star Architecture Slide 38 CC BY-SA richargh.de
  34. Characterized behavior Code is testable Domain Clusters „When will it

    be done“-Pressure Slide 39 CC BY-SA richargh.de
  35. Legacy means less predictability for “when it will be done”*

    * When you don’t have legacy code, your predictability mainly depends on how small you 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 40 CC BY-SA richargh.de
  36. Legacy means less predictability for “when it will be done”*

    * When you don’t have legacy code, your predictability mainly depends on how small you 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 41 CC BY-SA richargh.de
  37. Colorize based on predictability * Define appropriate criteria as a

    team. Increasing predictability Component ✓ Yes ✗ No Pattern: Quality Views Patron 2/10* Slide 42 CC BY-SA richargh.de ✗ Criteria 1 ✓ … ✗ … ✗ … ✓ … ✗ … ✗ Criteria n
  38. Colorize based on predictability * Define appropriate criteria as a

    team. Enforced component bounds (f.ex. via Arch-Unit tests) ✗ Characterization tests coverage 100% ✓ Functional Unit tests coverage +80% ✗ No obsolete or critically vulnerable depedendencies used ✗ Presentation-Infra-Domain Layering ✗ Component testable ✓ Behavior Structure Pattern: Quality Views Service-Level Agreements See appendix for more ideas…* Slide 43 CC BY-SA richargh.de Contract test coverage +100% ✗
  39. Communicate High-Level-View 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 44 CC BY-SA richargh.de
  40. Capability-View 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 45 CC BY-SA richargh.de
  41. By adressing these 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 46 CC BY-SA richargh.de
  42. Characterized behavior Code is testable Change here breaks there Domain

    Clusters Quality Views Slide 47 CC BY-SA richargh.de
  43. Map your Temporal Coupling 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 48 CC BY-SA richargh.de
  44. 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 49 CC BY-SA richargh.de
  45. Connascence of 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 50 CC BY-SA richargh.de
  46. Connascence guides refactoring Connascence: https://www.maibornwolff.de/en/know-how/connascence-rules-good-software-design/ 1. // A) Connascence of

    Type 2. enum MovieType { } 3. // B) Connascence of Type 4. sealed interface Movie permits RegularMovie { } 1. // A) Connascence of Name 2. static int OLD_PEOPLE_PENALTY = 25; 3. // B) 4. // appropriate solution is a team effort ☺ Connascence of Meaning Slide 51 CC BY-SA richargh.de 5. // C) Connascence of Name 6. interface Movie { 7. int amount(){ … } 8. }
  47. Let Connascence guide the decoupling Strong of Connascence Weak of

    Connascence Reduce Strength Increase Locality New Domain Concept Lock Element Locked Domain Concept Slide 52 CC BY-SA richargh.de
  48. Characterized behavior Code is testable Tests become blockers Decoupled some

    Domain Clusters Quality Views Slide 53 CC BY-SA richargh.de
  49. Tests can cement structure and block progress 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 54 CC BY-SA richargh.de
  50. 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 See also Java Aktuell 4/24 and https://richargh.de/posts/Structure-Cementing-Tests-1 Pattern Slide 55 CC BY-SA richargh.de
  51. 7. const book = a.book(); // I need a book,

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

    in domain See also Java Aktuell 6/24 and https://richargh.de/posts/Structure-Cementing-Tests-1 1. // create the low-level unit test-DSL 2. // small test, infrastructure ports are now stubs or fakes, they never connect to the real world 3. const { a, infrastructure } = unitTest().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.user(it => it.hasPermission(“CAN_RENT_BOOK”); // a user, don’t care who 9. 10. await a.saveTo(infrastructure); // store book and user entities in repositories 11. 12. const testee = configureRentingComponent(infrastructure); // 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 Slide 57 CC BY-SA richargh.de
  53. Characterized behavior Code is testable Innovation jam Outside-in Tests via

    Dsl Decoupled some Domain Clusters Quality Views Slide 58 CC BY-SA richargh.de
  54. When we safely work towards our goal … Presentation Domain

    Infrastructure Application delegates to AppleSauce1 DataOpsController BookController CreateBook RentBook handle handle Configurable Dependencies Configurable Dependencies Slide 59 CC BY-SA richargh.de
  55. … and so does predictable innovation* * When you don’t

    have legacy code, your predictability mainly depends on how small your make your work items Increasing predictability 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 62 CC BY-SA richargh.de
  56. Thank you richargh.de Richard Gross (he/him) IT Archaeology + Health

    Checks Hypermedia TestDSLs Works for maibornwolff.de/ richargh.de richargh Slide 63 CC BY-SA richargh.de Give CodeCharta a Give me a
  57. We could talk even more about patterns 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 65 CC BY-SA richargh.de
  58. 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 67 CC BY-SA richargh.de
  59. Passive Pattern: Map Coordination Bottlenecks Slide 68 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?
  60. 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. See also https://codescene.com/knowledge-distribution Slide 69 CC BY-SA richargh.de
  61. Passive Pattern: Map Knowledge Silos 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 70 CC BY-SA richargh.de
  62. 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 71 CC BY-SA richargh.de
  63. How do we refactor what we don‘t understand? Slides by

    @arghrich Slide 72 CC BY-SA richargh.de
  64. Slides by @arghrich 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 73 CC BY-SA richargh.de
  65. Why all the effort to re-discover? We could just start

    a-new! Slide 74 CC BY-SA richargh.de
  66. The True Cost of Feature-based Rewrites 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 75 CC BY-SA richargh.de
  67. 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 Slide 76 CC BY-SA richargh.de
  68. Generate and view a CodeCharta map 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 77 CC BY-SA richargh.de
  69. Communication Pattern: Quality Views Parking-Lot View 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 78 CC BY-SA richargh.de
  70. Invoicing Communication Pattern: Quality Views Even more detail Increasing changeability

    Better Worse Pricing Renting Locked Feature-planning often requires more details Focus Component Capability FE BE search quote book bill liquidate Slide 79 CC BY-SA richargh.de
  71. 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 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 80 CC BY-SA richargh.de
  72. Pattern: Complexity Invest 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 81 CC BY-SA richargh.de
  73. 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 infrastructure2 • Finally group things that only interact with each other and extract as new type • You now have new domain concepts to name 1 Flag arguments https://martinfowler.com/bliki/FlagArgument.html 2 Presentation Domain Data Layering https://martinfowler.com/bliki/PresentationDomainDataLayering.html Slide 82 CC BY-SA richargh.de
  74. Anti-Pattern: Package by Layer1,2 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 Infrastructure Layer It‘s all about the frameworks Models intermixed with unrelated models. Utils are a smell Slide 83 CC BY-SA richargh.de
  75. 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 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 84 CC BY-SA richargh.de
  76. commons Active Pattern: Package by Component1,2 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 85 CC BY-SA richargh.de
  77. The Domain gets top-level modules ? ? ? ? ?

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? PatronId Book BookOnHold patron book dailysheet inventory Slide 86 CC BY-SA richargh.de
  78. Slide 87 CC BY-SA richargh.de inventory dailysheet Patron An ordered

    mess emerges ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? PatronId Book BookOnHold Book
  79. Communication Pattern: Quality Views Colorize based on changeability 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-Domain-Infra 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 88 CC BY-SA richargh.de
  80. 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) Slide 91 CC BY-SA richargh.de
  81. 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 Slide 92 CC BY-SA richargh.de
  82. Our highest priority is to satisfy the customer by not

    changing what doesn’t need changing. The second principle of the legacy software manifesto (if one is ever written). Slide 93 CC BY-SA richargh.de
  83. A brief coupling primer High Coupling Low Cohesion Low Coupling

    High Cohesion Coupling Unknown Source Slide 96 CC BY-SA richargh.de
  84. 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 97 CC BY-SA richargh.de
  85. 4-axes of Connascence Strength Level How explicit Locality How close

    Degree Number of Impacts Volatiliy How much change Slide 98 CC BY-SA richargh.de
  86. The 4½ types of testing 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 99 CC BY-SA richargh.de
  87. Metamorphic testing Input Output testee x f(x) g(x) g(f(x)) testee

    Derive via metamorphic relation Assert Check if relation holds Slide 100 CC BY-SA richargh.de