Home Working effectively with legacy code
Post
Cancel

Working effectively with legacy code

We are the the source of software is software developers.

Neglect is contagious. Once, one a problem happens, other developers tend to do more bad things to it. On going negligence. This causes the software to rot.

Symptoms of rotten software (At system level):

  • Rigidity: Change one thing leads to another, the structure of the system resists change. A software system that resist change becomes hardware.Software was invinted so we can change the behaviour of our machines.
  • Fragility: Touch the code in one place, it misbehaves in another. usually, is something totally unrelated. A fragile system breaks in unexpected ways and it fills managers and customers terrified. It breads intrust.
  • Immobility: Hard to distangle the system, we can’t use the parts parts which should be geeneric in other parts.
  • Viscosity: Makes it easy to do the wrong things. The tooling and everything around it makes hard to do the right hting. Test suite or compile in hours, makes it easier to do the wrong thing, which is not runnning the test.
  • Needless complexity: Usually, happens when the designers anticipated systems that a more complex than it needs to be. (pre-design YAGNI). Donn’t get carried away with the needless complexity.
  • Needless repetition: Copy-Paste… Programmers too lazy to unify, bug and design flow repeats all over the place
  • Opacity: Inability to look at the code an understand it.

Code Smells (software level): Refactoring by Martin Fowler New copy is coming out.

  • Duplication of code (Dry)
  • Long methods: How short should they be five to six lines. If you can extract one function from another (extract until you can’t anymore) ….

Remedies: ###Manage Dependencies:

  • Source code dependencies, import, use, include statements.
  • We don’t want the business rules depending on the GUI, we don’t want changes in the GUI to force business rules to be recompiled.
  • We don’t business rules depend on database. A schema change, shouldn’t result in recompilation in business modules.

SOLID:

  1. Single Responsibility Principles
  2. Open/Close Principle
  3. Liskov Substitution Principle
  4. Interface Segregation
  5. Dependency Inversion

A class: A group of function that operate on the same data structure. In functional programming, functions pass around a single data structure, so it still applies.

Attributes of good design:

  • It reveals its intent: It tells you what the application does, not what the framework that is ues. Even on directory structure.
  • Adaptable: Tolerate evolution.
    • Isolate things that change for one reason from things that change for another reason.
    • Accept new technologies: If you adapt new technology, doesn’t require redesigning a new technology. (Our application shouldn’t know what IO we are using, specially in the business rules)

Clean Code

  • Clean code separate levels of details: Crosses multiple levels of structure. Every line of code in a function should live in the same level of detail. Shouldn’t null check (low level), and business check (high level). Every function should have steps a one level below its level of abstraction. and it could call functions at lower levels. Clean Code Tells a Story
  • Clean code doesn’t need few comments: The comment implies the code that is near it needs explaination, which implies that the code is written badly. Sometimes the comment is required, but that is rarely. Comments, are a result of inability to write code that explains itself. Make comments standout and annoying in IDE because you have to read it, and delete it if necessary.
  • Intention-revealing code: findById(id) is better than binarySearch(id)
  • Small methods:
  • CQRS:
    • Any function that returns void, it changes the state of the system (Command). Can return operational code, but not business values.
    • Any function that returns a value it shouldn’t change the state of the system (Query)

Professionalism

  • Understandable code doesn’t just happen
    • Professional commitment
    • Any fool can write…. ~Martin Fowler
    • You have to craft it and clean it.
    • First responsibility as a programmer is to write code that others can understand. Your first responsibility is your coworkers.
    • It might work fine now, if requirement changes, if I can’t understand it, it is useless. It is better to have a code that doesn’t work and understandable, because you enable it for everyone.
  • The only way to get fast is to do it well.
  • There is not enough time to wash your hands as doctor. It is the same as cleaning code.
  • Code that:
    • Delivers business value
    • clean
    • tested
    • simple

Test Driven Development

The three steps. Rinse and repeat every minute.

  • If you have a team of 20 people, and you pick any of them at random, you will have a code that was working a minute ago.
  • Being good at using the debugger is not a skill to be desired.
    • If you’re doing circles of one minute. The fastest way to debug is to delete the last one minute change and go back to a Green state
    • Testing code is the source of truth. Documentation that describes how the system works in details. and can’t get out of sync of the code.
  • Code is too scary to change is now owns you, and you submit to it. The good test code: gives you the power to dominate the code.
  • Double entry bookkeeping.
  • Code coverage should be private, the worst thing to do is to demand some level coverage (e.g. in the build) People would be creative to up their coverage (e.g. remove asserts)
  • It is possible can drive into terrible design, it is just a tool.

SOLID

We maneuver the code to be SOLID we don’t design the code to be SOLID. Whiteboard and design in the beginning, but don’t believe what you designed. Tests gives you the power to maneuver safely.

  • Agile Software Development: Robert C. Martin

Single Responsibility Principle:

  • A class should have one and only one reason to change. The reason to change is people. Different stakeholders. Sources of changes should be orthogonal.
  • Employee class, has three methods with multiple interest groups:
    • CFO: caclulatePay()
    • CTO: save()
    • COO: printReportt() -> Formatting and business rules.
    • You change printReport() and the CFO is the one who fires you!
  • Structure of the application memics the structure of the people who use it.
  • Violation result in
    • Difficult to understand (intertwined rules)
    • Change for so many reasons
    • Subject to merge collisions (multiple people changing different modules for different reasons)
    • transitive impact
    • Subject to deployment thrashing.

Open-Closed Principle

  • Software modules should be open for extension and closed for modification.
  • We mostly do this through poly morphism.
  • I don’t even have to recompile it
  • How to do that?
    • create new instances that respect the interface. Extend the application by adding a new sub-class.

The Liskov Substitution Principle

  • Inheritance is not really is a, integer doesn’t inherit from real, even though it is a real number. The correct thing is behave as
  • The caller doesn’t care/know about the derivatives. (Base class shouldn’t know about the derivatives)
  • Violation Liscove principle eventually causes a violation of open close principle.
  • Add first and then downcast.

The interface segregatoin Principle

  • Clients should only depend on the methods they call. If one depend on a class, you depend on all methods of that class even if you use them or not. You will recompile if one of them change.
  • There are more methods than any one client needs.
  • Every client depends on an interface that has only the methods it needs.
  • This is not about inheritance, it is generally: Don’t depend on things you don’t need.

Dependency Inversion Principle (Depend on abstractions)

  • Low level should depend on high level policy, not the other way around.
  • In normal programming high level functions call lower level functions and so forth.
  • Fix that by using interfaces. The source code dependency can oppose the flow of control.
  • Switch statements usually violates dependency inversion principle. High level module depends on low level modules.
  • Details pollutes the upper level policy.
  • High level policy depend on abstraction (testable, reusable, tell a story)
  • Everytime you mention the name of the concrete class you are violating the principle (new keyword always violate this principle, hence we tend to hide those in factories)
  • Factory interface is in the high level policy.
  • If derived classes are going to be very volatile, you could use a one method with string as argument, which violates type safety, but fully complies with the rule.

Rules for simple design (In the same order)

  • Keep all tests passing
  • Eliminate duplication, DRY
  • Express intent
  • Minimize number of methods and classes

Automated Tested

  • Tests should be run often
  • Manual test are expensive to run (it would be better if we have none). Anything can be scripted should be automated.
  • Automated tests cost little to run

Notes:

Never create a project to clean the system, this will fail and no one will trust you again. Change the attitude: boyscout rule, a little bit cleaner, a random act of kindness. You probably, can’t do a lot of refactoring because there is no tests.

  • Essential complexity is the complexity of the problem at hand. Accidental or unessential complexity, adding hooks for all the features that might ever happen, which might never happen.
  • I hate checked exception: they have the tendency to make high level code care about the low level complexity.

-> Book: Working effectively with legacy code

This post is licensed under CC BY 4.0 by the author.
Contents

Managing Cross Concerns using Proxy Pattern

Creating pipelines using channels in Go

Comments powered by Disqus.