Emergent Design Strategies Using Refactoring

Explore top LinkedIn content from expert professionals.

Summary

Emergent design strategies using refactoring focus on evolving software architecture by making small, safe improvements to existing code, rather than planning every detail upfront. Refactoring means restructuring code without changing its external behavior, allowing new designs to emerge naturally as developers respond to real needs and challenges.

  • Start with tests: Build a safety net of tests around your code so you can confidently make changes without unintentionally breaking functionality.
  • Isolate and improve: Identify small sections of code to work on, then gradually simplify and reorganize them while keeping everything running smoothly.
  • Push dependencies outward: Move external dependencies away from core logic, making your codebase easier to extract, reuse, and maintain as it grows.
Summarized by AI based on LinkedIn member posts
  • View profile for Emily Bache

    Samman Technical Coach at Bache Consulting

    6,473 followers

    I recently heard a story about Ward Cunningham coding, (Ward is one of the pioneers of eXtreme Programming, inventor of the Wiki and all-round brilliant engineer). Ward sat down with some code he’d never seen before and almost immediately started working on it. He couldn’t possibly have read and understood all that code in that time, yet he was happy to begin changing it and working towards new functionality. Getting stuff done. That’s in sharp contrast to the way many developers approach unfamiliar code. Most people are afraid to change code they don’t fully understand, and will spend a great deal of effort reading and analyzing it before doing anything. Ward’s approach is much more direct, and ultimately, faster to achieve results. In my mind, the fundamental difference is one of skill. Great engineers are not afraid to change code because they can refactor safely. Refactoring means improving the design of code without changing the externally observable behaviour. In his book Fowler describes how to do Refactoring safely and effectively - using many small safe steps to gradually transform a design from one state to a better one, without the functionality ever being changed or broken in the meantime. When you watch someone working this way it almost feels like nothing is happening, just small tweaks here and there, until suddenly you realize you have got to a much better structure with more understandable names, higher cohesion and less coupling overall. New features become easy to add once the structure is in a better state. Most developers who want to change the design will almost immediately break the functionality and/or the compilation, and not get everything working again for (best case) several minutes, or (more typically) a few hours. If you use the patterns and principles originally described by Fowler then the way you work will be qualitatively different. The code is almost never in a broken state, only for a few seconds or a minute or two at most. This is not always easy to achieve, it takes skill, but if you can do it there are many advantages: - Get started straight away even without understanding all the code. - Achieve an improved design sooner. - Less likely to introduce bugs or change functionality unintentionally. Refactoring is a hugely learnable skill and well worth the effort. It can speed up all your design work, which is, if we’re honest, the most important and difficult task developers spend their coding time on.

  • View profile for Yves Goeleven

    I help software engineering teams get better at delivering value.

    7,716 followers

    Extracting parts of a legacy system Test from the outside, refactor from the inside This strategy is based on the idea of refactoring existing capabilities in such a way that they become autonomous. I recommend to work on a very small sliver of the application at a time, e.g one call stack (e.g. an API route). 1. Test from the outside The first thing you want to do is assess the quality of the tests, if any exist. Assuming there are none, or insufficient tests available, then you best start by building a safety net on the outside of the chosen call stack . - Identify the shortest branch in the call stack. - Write a verification test for the existing behavior of this branch, e.g. a wire compatibility or snapshot test. - Use a code coverage tool to identify all branches in the call stack - Repeat for all branches in the call stack, from short to longer, until you have sufficient coverage of the entire call stack. Once at least some tests are in place (100% code coverage is not a must), you can start refactoring from the inside. 2. Refactor from the inside You want to start refactoring from the inside of the call stack, as the code in the deepest branches will typically have the least dependencies and often represent (a part of) what could become a functional core. The goal is to remove any dependencies from the core. - Identify functional core(s), usually found in the deepest branches of the call stack. - Move any dependency to the boundary of the core. - Run the safety net to verify existing behavior did not get broken. - Add unit tests for the isolated functional core while you're at it. - Repeat these steps for all identified cores, until you reach the top of the call stack. 3. Refactor the top of the callstack After working up the callstack, and pushing more and more dependencies to the boundary, while bringing the identified cores together, an imperative shell should be the only code left having dependencies. The code will still be residing at the top of the call stack, and should now also receive the 'io to the boundary' treatment. - Move any dependencies to the boundary of the component. - Create integration tests for all dependencies. - Abstract the dependencies. - Consider changing the nature of the dependencies, e.g. subscribe to changes from a dependency instead of querying its database. - Create validated test doubles for the dependencies, using contract tests. - Inject the dependencies through Inversion of Control (IoC). - Implement component tests to validate the interaction between the core and the abstacted dependencies. - Repeat for all dependencies of the component. 4. Extract Your vertical slice of the application should now be completely independent from other parts of the system, allowing you to extract it and run it autonomously.

  • View profile for Zoran Horvat

    Principal Architect & Software Strategy Consultant

    24,295 followers

    The primary problem with changing existing code is 𝗿𝗲𝗴𝗿𝗲𝘀𝘀𝗶𝗼𝗻 - an unfortunate sequence of events where developing one feature causes another feature to stop working. 𝗛𝗼𝘄 𝗱𝗼 𝘄𝗲 𝗳𝗶𝘅 𝘁𝗵𝗮𝘁? 𝗧𝗵𝗲 𝗶𝘁𝗲𝗿𝗮𝘁𝗶𝘃𝗲 𝗱𝗲𝘃𝗲𝗹𝗼𝗽𝗺𝗲𝗻𝘁 technique offers a helping hand. It teaches us to make changes in microscopic steps, ensuring that the entire application keeps working correctly throughout the entire process. Iterative development relies massively on 𝗿𝗲𝗳𝗮𝗰𝘁𝗼𝗿𝗶𝗻𝗴. But refactoring alone is not sufficient. Let me introduce you to a broader, more powerful process that will help you develop a feature through a series of refactoring and designing steps. 𝗧𝗵𝗲 𝗽𝗿𝗼𝗰𝗲𝘀𝘀 𝗰𝗼𝗻𝘀𝗶𝘀𝘁𝘀 𝗼𝗳 𝗿𝗲𝗽𝗲𝗮𝘁𝗶𝗻𝗴 𝘁𝗵𝗲𝘀𝗲 𝗳𝗼𝘂𝗿 𝘀𝘁𝗲𝗽𝘀: 𝟭. 𝗜𝗦𝗢𝗟𝗔𝗧𝗘 - Isolate the smallest piece of code that requires development; enclose it in a dedicated method. 𝟮. 𝗜𝗠𝗣𝗟𝗘𝗠𝗘𝗡𝗧 - Make changes to that method while preserving its signature and externally visible behavior; by doing so, you preserve all other features. 𝟯. 𝗜𝗡𝗝𝗘𝗖𝗧 - Pull out the implementation into its own class(es); introduce an abstraction if necessary. 𝟰. 𝗜𝗠𝗣𝗥𝗢𝗩𝗘  - Apply refactoring and redesigning to the new types while preserving their public interface. You can learn more about this tactical coding maneuver from the video below. The video explains this process in more detail, and demonstrates its application on a Web application. https://lnkd.in/dtJD2pDY

Explore categories