Upskill tutorial for decoupling

Hud Wahab
5 min readJul 3, 2023

--

Day 3: Abstraction. Protocol. Functions.

Hi 👋 I am Hud, a postdoc for engineering data science at the AI Manufacturing Center in Laramie, Wyoming. My funding is running out (AaAaaA !), so while I am actively looking for a new job, instead of doing the 205th coding certificate to prove my worthiness — I thought I’d do design challenges and document how I spend my time upskilling so other engineers can do the same.

Nowadays, certificates are everywhere. Documenting small upskill projects that you can later show off is the best way to get recognition as a professional engineer.

This is day 3 of a 30-day design challenge. Follow along and let me know if you get stuck!

TL;DR Decoupling challenge

Download the provided code for the challenge.

Challenge:

  1. Identify the current code structure and its limitations:
  • Analyze the BankService class in bank.py.
  • Understand the high coupling between the BankService class, payment service, and account types.
  • Identify areas of code duplication.

2. Plan the refactoring strategy:

  • Determine the desired level of decoupling between banking and payment operations.
  • Decide on an approach to reduce coupling and eliminate code duplication.
  • Consider introducing new classes or replacing existing classes with functions, as needed.

3. Refactor the code:

  • Modify the BankService class and related components to reduce coupling.
  • Extract common code and eliminate duplications.
  • Consider creating separate classes or functions for banking and payment operations.

4. Test the refactored code:

  • Ensure that banking operations are decoupled from payment operations as intended.
  • Verify that the refactored code retains the desired functionality.
  • Conduct thorough testing to validate the correctness of the changes.

5. Evaluate the effectiveness of the refactoring:

  • Assess the impact of the refactoring on code structure, maintainability, and readability.
  • Determine if the desired level of decoupling and code reuse has been achieved.
  • Consider soliciting feedback from peers or conducting code reviews to gather different perspectives.

6. Document the changes:

  • Update relevant documentation or comments to reflect the refactored code.
  • Provide clear explanations of the changes made and their benefits.
  • Document any new classes or functions introduced during the refactoring process.

7. Review and iterate:

  • Review the refactored code and documentation for accuracy and completeness.
  • Iterate on the refactoring, if necessary, to address any overlooked issues or further enhance the codebase.

Remember to maintain a focus on reducing coupling, eliminating code duplication, and improving the overall design and maintainability of the banking service.

STEPS

I identified some parts of the code excerpt from bank.py that have high coupling and low cohesion:

  1. The deposit method in the BankService class has low cohesion because it performs multiple tasks, including printing a message, processing a payment, and updating the account balance. These tasks are not closely related and should be separated into separate methods to improve the code's maintainability.

2. The deposit method in the BankService class has high coupling with the SavingsAccount and CheckingAccount classes. This is because the method takes an account object as an argument and then checks its type using isinstance. This creates a tight coupling between the BankService class and the SavingsAccount and CheckingAccount classes, which makes the code less maintainable.

3. The payment_service object in the deposit method has high coupling with the StripePaymentService class. This is because the BankService class creates an instance of the StripePaymentService class and calls its methods directly. This creates a tight coupling between the two classes, which makes the code less maintainable.

Create a payment service abstraction

We can include aPaymentService protocol that includes two methods: process_payment and process_payout. By defining a protocol, we can reduce coupling between the BankService class and any concrete payment service implementation.

Instead of directly instantiating a concrete payment service class like StripePaymentService in the BankService class, we can use dependency injection to pass in an instance of a class that conforms to the PaymentService protocol. This allows us to easily switch between different payment service implementations without modifying the BankService class.

For example, if we wanted to switch from using StripePaymentService to PayPalPaymentService, we could simply create an instance of the PayPalPaymentService class that conforms to the PaymentService protocol, and then pass it into the BankService constructor. The BankService class would not need to be modified, since it only depends on the PaymentService protocol, not on any concrete payment service implementation. Also, at this point, we don’t need to import this any longerfrom stripe_service import StripePaymentService .

Abstract away an Account class

Next, by using a dataclass, we can define the attributes of the Account class in a concise and readable way. This can help to reduce coupling between the BankService class and the Account class, since the BankService class only needs to know about the attributes and methods of the Account class, not how they are implemented.

The deposit and withdraw methods allow us to modify the balance attribute of the Account class. By encapsulating the logic for depositing and withdrawing money within the Account class, we can further reduce coupling between the BankService class and the Account class.

Use functions!

Using functions can provide several advantages in software development. Here, we use functions like deposit and withdraw can help to improve modularity and reusability.

By encapsulating the logic for depositing and withdrawing money within separate functions, we can make the code more modular and easier to maintain. For example, if we wanted to add a new payment processing step to the deposit function, we could do so without modifying the Account class or the withdraw function.

Functions can also be reused in different parts of the codebase, which can help to reduce code duplication and improve consistency. For example, if we had another part of the codebase that needed to process payments, we could reuse the deposit and withdraw functions instead of duplicating the payment processing logic.

The Account class and the deposit and withdraw functions together provide the functionality of the BankService class in the previous implementation. By encapsulating the logic for depositing and withdrawing money within the Account class, and by using the PaymentService protocol to abstract away the details of the payment processing, we can completely replace the BankService class with a simpler and more modular implementation.

Compare the main code

The code after is more decoupled than the code before in the main function of main.py. The deposit and withdraw functions in the code after only depend on the Account class and the PaymentService protocol, while the main function only depends on the deposit and withdraw functions and an instance of a class that conforms to the PaymentService protocol.

In contrast, the main function in main.py depends on the BankService class and the SavingsAccount and CheckingAccount classes, which are tightly coupled. This can make the code more difficult to maintain and modify in the future.

Conclusion

Congratulations! You finished Day 3 from the 30-day design challenge.

If you have reached this far, you know how to:

  • Identify high coupling
  • Use protocol to abstract different roles and functions
  • Simplify the code using functions

Check out the day 4 challenge!

Also, you can access the full 30-day GitHub repository here.

💡 My goal here is to help engineering data scientists upskill in design. I’d like to hear from you! Was this helpful? Anything I can improve? Connect with me on LinkedIn | Medium

--

--

Hud Wahab

🤖 Senior ML Engineer | Helping machine learning engineers design and productionize ML systems. | Let's connect: https://rb.gy/vb6au