Sid Ngeth's Blog A blog about anything (but mostly development)

Welcoming Dumb Ideas

0 interest is an interest level

I just stumbled upon a viral-ish X post where most of the commenters made fun of Grant Cardone, a known “grifter” in a lot of people’s eyes: Grant Cardone has an IQ of 42 and is worth $600 million. What is holding you back?

A comical thesis he proposed was that “0 interest, is an interest level”. Which I don’t recommend you try unless you want HR to randomly Zoom you.

When you have a perception of a person, it’s hard to not have a certain preconceived bias. Instead of taking it face value I thought about it a bit more. Is there any possible nugget of wisdom or something we can learn here? While you may not need to agree 100%, you should be aware of these biases that might potentially shoot down ideas pre-emptively.

After some more thought, I believe what he was trying to say is when you want to sell something to someone who has 0 interest level, it’s not the end of the world. The “0 interest level” is a litmus or calibration point to effectively tell you where you stand in this person’s mind.

In software engineering, we are constantly exchanging ideas back and forth and trying to build consensus. Believe it or not, one does not simply read How to Win Friends and Influence People and become an expert in soft skills as repeatedly pitched by managers around the world.

My personal problem with the book is some what summarized from another Reddit user:

However, after 80 years in the field, its techniques have been vastly surpassed by later developments in human psychology and influence. I’m in a role where I have to deal with dozens of salesmen hitting me up every week. If I see a Dale Carnegie technique (‘complementing’, ‘using my name’, ‘talking in terms of my interests’) I know I’m dealing with someone very new in a sales role.

Although the principles I will highlight later make them quite evident, I find that they need more concrete ways of applications as a day to day software developer.

Something I experienced in my last company was the idea of Test Driven Development or Testing culture in general. Principal engineers would vouch for the bugs saved or the cost savings from re-work. Being able to be agile and move fast while having regression safety net. On the flip side there are cultures that do not embrace testing as it slows them down, etc. Regardless to say, testing was mandated, but how effective was it exactly? Thats not the topic of this post in particular, but the semination of ideas into your organization.

Building organizational consensus for effectuating change:

Build the why:

Listing benefits that people already know isn’t going to necessarily help in my experience. Non technical people prefer ROI/Cost savings, data helps. Listening and solving their major pain points from their point of view helps. If you are just simply trying to practice resume driven development or over optimize/refactor a piece of your code base with no convincable benefits, you are probably going to get shot down. Avoid Shiny Object Syndrome for the sake of shiny.

Principle 1: Fundamental Techniques in Handling People. By listening to their ideas and solving their major pain points, you’re showing genuine interest in their perspective and fostering a positive relationship.

On a random side note on Clean Code. I highly recommend this very informative debate between Bob Martin and Casey M: https://github.com/unclebob/cmuratori-discussion/blob/main/cleancodeqa.md It’s a very good case study of two programmers coming from two different backgrounds on why they believe in their philosophies.

Provide Education and Training for Ease of adoptability:

Offer training on the importance, methodologies, and best practices. Empowering team members with knowledge helps them embrace the change confidently. To introduce a new process or idea, it must be low barrier or small investment for users to adopt. If it’s something completely ridiculous and creates more work it’s obviously a no.

Having an “escape hatch” or a backup plan can provide reassurance and mitigate the fear of failure when implementing changes in a company. It allows for a safety net in case the change doesn’t yield the expected results or faces unforeseen challenges. This backup plan can include alternative strategies, contingency measures, or even a way to revert to previous processes if necessary.

Principle 2: Six Ways to Make People Like You. By offering training on the importance, methodologies, and best practices, you’re demonstrating genuine interest in their development and success. Empowering team members with knowledge helps them embrace the change confidently and builds trust and rapport.

Involve your team early:

I think a more effective approach is talking to your co-workers one by one, and listening to their ideas. You can then achieve better overall buy-in now that everyone is on board. If you can then sell why your new idea or approach is saving them from a pain point(or for non technical people, make $), you are going to better be able to get consensus. Include team members in decision-making from the start, seeking their input and concerns. This fosters ownership and commitment to the change.

Principle 3: How to Win People to Your Way of Thinking. By including team members in decision-making from the start and seeking their input and concerns, you’re showing respect for their opinions and making them feel valued. Additionally, your emphasis on selling the benefits of your ideas by addressing their pain points ties into the principle of appealing to the other person’s interests.

Conclusion

Sometimes dumb ideas can have benefits that you never thought. It’s more of a matter of building the proper consensus with your team. Tying back into the idea that a company culture that embraces psychological safety will have a lot more potential for innovation compared to ideas simply mandated from an ivory tower. If all else fails just become the CEO of your own company 😂

Embracing the Weight

Confronting Impostor Syndrome in Software Engineering and Powerlifting

In the realms of both software engineering and powerlifting, there exists a common adversary that haunts the minds of many practitioners: impostor syndrome. This psychological phenomenon, characterized by feelings of inadequacy and self-doubt despite evident competence, manifests itself in strikingly similar ways in these seemingly disparate domains.

Imagine stepping into a weight room, the clinks of iron plates echoing as seasoned lifters go about their routines with apparent ease. As a novice, each lift feels like a monumental task, and the specter of failure looms large. Similarly, in the world of software engineering, entering a new project or tackling a complex problem can evoke that same sense of trepidation. Despite possessing the requisite skills and knowledge, doubts creep in, whispering that perhaps you don’t belong among the ranks of your peers.

Yet, it is precisely within these moments of doubt and discomfort that the parallels between powerlifting and software engineering reveal themselves most vividly. Consider the act of progressively overloading in powerlifting, where lifters continually push themselves to lift heavier weights. The process is not one of the weights becoming easier; rather, it is the lifter who grows stronger, adapting to the increasing demands placed upon them.

Similarly, in software engineering, the challenges we face do not diminish with experience; if anything, they become more complex and nuanced. Yet, it is through confronting these challenges head-on, stepping outside our comfort zones, that we cultivate resilience and expertise. Each bug squashed, each feature implemented is akin to adding another plate to the bar, pushing ourselves into the realm of the unknown and emerging stronger for it.

Moreover, both powerlifting and software engineering are communal endeavors, where individuals often find support and camaraderie within a community of peers. Just as a powerlifter may seek guidance from more experienced lifters to refine their technique and overcome plateaus, software engineers can benefit immensely from mentorship and collaboration. By fostering an environment where vulnerability is embraced and knowledge is shared freely, we can alleviate the burdens of impostor syndrome and empower each other to reach new heights.

Continuous Improvement: The Shared Path of Powerlifting and Software Engineering

At the heart of both powerlifting and software engineering lies a fundamental principle: the pursuit of continuous improvement. In powerlifting, this manifests as the relentless quest to lift heavier weights, to surpass previous personal bests, and to refine lifting technique for optimal performance. Similarly, in software engineering, the ethos of continuous improvement drives us to refine our coding practices, to seek out more efficient algorithms, and to constantly expand our knowledge base.

Just as a powerlifter meticulously tracks their progress, recording every lift and analyzing each session for areas of improvement, software engineers employ methodologies like Agile development to iteratively refine their work. Through practices such as regular code reviews, retrospectives, and continuous integration, we strive to identify weaknesses, address shortcomings, and evolve our skills over time.

Moreover, both powerlifting and software engineering recognize that the path to mastery is not a linear one. Setbacks and failures are inevitable, but it is how we respond to these challenges that ultimately defines our progress. Just as a missed lift serves as a learning opportunity for a powerlifter, a software bug or a failed deployment can provide valuable insights for an engineer. By embracing failure as an essential part of the learning process, we foster resilience and adaptability, propelling ourselves ever closer to our goals.

Furthermore, the concept of continuous improvement extends beyond individual practice to encompass the broader communities of powerlifting and software engineering. Lifters share training tips, programming strategies, and motivational support with one another, while software engineers collaborate on open-source projects, contribute to online forums, and attend conferences to stay abreast of the latest developments in the field. In doing so, we create a culture of learning and growth, where knowledge is freely exchanged, and innovation flourishes.

Culture Beats Talent: Cultivating Fearlessness in Collaboration

In the realms of powerlifting and software engineering alike, it’s often said that culture beats talent. While raw skill and technical prowess certainly have their place, it is the collective ethos of a team that truly sets the stage for success. In both disciplines, cultivating a fearless, collaborative culture can spell the difference between mediocrity and excellence.

Consider the powerlifting gym, where lifters of all skill levels gather to train, support, and motivate one another. Here, it’s not just about individual achievement but about lifting each other up—both literally and metaphorically. Seasoned lifters mentor novices, sharing their knowledge and expertise, while beginners infuse the gym with a sense of enthusiasm and camaraderie. Together, they create an environment where everyone feels empowered to push their limits, knowing that they have a community behind them every step of the way.

Similarly, in software engineering, the most successful teams are those that foster a culture of psychological safety, where team members feel comfortable taking risks, sharing ideas, and admitting mistakes without fear of judgment or reprisal. It’s a culture where egos are checked at the door, and collaboration takes precedence over individual glory. In such an environment, innovation flourishes, as diverse perspectives and complementary skill sets converge to solve complex problems with creativity and agility.

Moreover, a fearless, collaborative culture in both powerlifting and software engineering serves as a bulwark against the insidious influence of impostor syndrome. When individuals feel supported and valued by their peers, they are less likely to succumb to self-doubt and more inclined to embrace challenges head-on. It’s not about being the strongest lifter in the gym or the most brilliant coder in the room; it’s about contributing to something greater than oneself and lifting each other up in the process.

Ultimately, whether we’re striving to lift heavier weights or to write more elegant code, the power of collaboration cannot be overstated. By cultivating a fearless, collaborative culture—one that celebrates diversity, encourages experimentation, and values collective achievement—we can overcome the limitations of talent alone and achieve feats that would be impossible on our own.

Practical Advice: Navigating Impostor Syndrome with Powerlifting and Software Engineering

In the face of impostor syndrome, it’s essential to arm ourselves with practical strategies for overcoming self-doubt and forging ahead on our journey of growth and self-improvement. Here are three actionable tips to help navigate the challenges of impostor syndrome in both powerlifting and software engineering:

  1. Set Realistic Goals: Break down your long-term aspirations into smaller, achievable milestones. Whether it’s increasing your deadlift by 10 pounds or mastering a new programming language, setting clear, measurable goals can provide a sense of direction and progress.

  2. Seek Support and Mentorship: Don’t be afraid to reach out to more experienced lifters or seasoned software engineers for guidance and support. Mentorship can provide invaluable insights, encouragement, and perspective, helping to dispel feelings of isolation and inadequacy.

  3. Celebrate Your Progress: Acknowledge and celebrate your achievements, no matter how small they may seem. Whether it’s completing a challenging workout or successfully debugging a piece of code, take pride in your accomplishments and use them as fuel to propel you forward on your journey.

By incorporating these practical tips into your practice, you can cultivate a mindset of resilience, self-confidence, and growth, both in the weight room and in the world of software engineering. Remember, overcoming impostor syndrome is not about eliminating self-doubt entirely but rather learning to navigate it constructively, using it as fuel to propel you forward on your journey of self-discovery and mastery. Embrace the challenges, seek support from your peers, and never underestimate the power of continuous improvement and collaboration. With perseverance and determination, you’ll not only conquer impostor syndrome but emerge stronger and more resilient than ever before.

Beyond Documentation: The Art of Reverse Engineering in Development

Introduction:

In the world of software development, documentation is often hailed as the cornerstone of understanding and maintaining complex systems. Whether it’s meticulously crafted API references, detailed architectural diagrams, or comprehensive user manuals, documentation serves as the guiding light for developers navigating through the intricate labyrinth of code. However, while documentation undoubtedly offers valuable insights into the inner workings of a system, it’s essential to recognize that it’s not always the silver bullet it’s made out to be.

In the fast-paced reality of the software industry, adaptability and the ability to learn quickly are paramount. Throughout my career, which began in the realm of Ruby on Rails development, I’ve encountered diverse systems, often without the luxury of comprehensive documentation. From navigating through C# and .NET to delving into Java and Spring development, and even spearheading migrations from JQuery to React, each transition has underscored the importance of adeptness in reverse engineering principles. In environments where documentation may be scarce or outdated, the ability to dissect and comprehend existing codebases becomes invaluable for effective problem-solving and innovation. This journey through various technologies has shaped my perspective on the symbiotic relationship between documentation and reverse engineering.

The Myth of Documentation as a Silver Bullet:

Before delving into the depths of reverse engineering, it’s crucial to acknowledge the merits of documentation. After all, well-written documentation can significantly streamline onboarding processes, facilitate collaboration among team members, and provide crucial context for future enhancements or bug fixes. From high-level overviews to granular code comments, documentation comes in various forms, each serving its unique purpose.

  1. API Documentation: API documentation provides developers with a roadmap for interacting with external services or libraries. It outlines available endpoints, parameters, response formats, and usage examples, empowering developers to leverage third-party functionality efficiently.

  2. Architectural Documentation: Architectural diagrams, such as UML diagrams or system flowcharts, offer a bird’s-eye view of the system’s structure and components. They help developers understand how different modules interact with each other and how data flows throughout the system.

  3. Code Comments and Inline Documentation: Within the code itself, comments and inline documentation serve as invaluable signposts for understanding implementation details, rationale behind design decisions, and potential pitfalls to watch out for. Well-documented code is not only easier to maintain but also fosters knowledge sharing and code reuse across teams.

While documentation undoubtedly offers numerous benefits, it’s essential to recognize its inherent limitations. No matter how comprehensive or meticulously maintained, documentation inevitably suffers from a few common pitfalls:

  • Incompleteness: Despite the best intentions, documentation is rarely exhaustive. It’s challenging to capture every edge case, exception, or subtle nuance in written form, leading to gaps in understanding for developers.

  • Outdated Information: In fast-paced development environments, code evolves rapidly, often outpacing the corresponding documentation. As a result, developers may find themselves relying on outdated or inaccurate information, leading to confusion and frustration.

  • Lack of Context: Documentation provides a snapshot of the system at a particular point in time, but it often lacks the contextual richness necessary for fully understanding the underlying rationale or trade-offs behind design decisions.

Reverse Engineering a Full-Stack CRUD Feature in Rails:

Imagine you’re a new engineer tasked with understanding and modifying a full-stack CRUD (Create, Read, Update, Delete) feature in a Ruby on Rails application. At first glance, the codebase may appear daunting, with multiple layers of abstraction and interconnected components. However, by applying the principles of reverse engineering, you can systematically unravel the complexities and gain a deeper understanding of how the system functions.

Understanding the MVC Pattern:

Ruby on Rails follows the Model-View-Controller (MVC) architectural pattern, which divides an application into three interconnected components:

  1. Model: Represents the data and business logic of the application. In Rails, models typically correspond to database tables and encapsulate operations such as querying, creating, updating, and deleting records.

  2. View: Handles the presentation layer of the application, rendering HTML templates and responding to user interactions. Views in Rails are often written in embedded Ruby (ERB) or utilize front-end frameworks like React or Angular for more complex interfaces.

  3. Controller: Acts as an intermediary between the model and view components, handling incoming requests, processing user input, and orchestrating the flow of data. Controllers in Rails contain action methods corresponding to CRUD operations (e.g., create, index, show, update, destroy).

Reverse Engineering Process:

  1. Start with the Controller: Begin your reverse engineering journey by examining the controller responsible for the CRUD action you’re investigating. Locate the corresponding action methods, such as create, update, or destroy, and analyze their logic and interactions with the model layer.

  2. Explore the Model Layer: Dive deeper into the model layer to understand how data is structured, validated, and persisted in the database. Identify the ActiveRecord models associated with the CRUD action and inspect their attributes, associations, and callbacks.

  3. Inspect the View Templates: Next, inspect the view templates associated with the CRUD action to understand how data is presented to users. Look for ERB files or front-end framework components that render forms, tables, or other UI elements relevant to the CRUD operations.

Deeper Debugging Principles and Skills in Rails Web Applications:

In the realm of Rails web applications, mastering debugging principles and skills beyond basic breakpoint-based debugging can significantly enhance a developer’s ability to diagnose issues, both in development and production environments. Let’s explore some advanced techniques and tools that senior developers employ to tackle complex problems effectively.

Debugging in JavaScript Environments:

In Rails development, debugging often involves navigating through the server-side codebase to identify and resolve issues. While tools like Pry, Byebug, and Rails console offer invaluable insights into application state and behavior, developers can also leverage browser-based debugging tools to diagnose frontend issues.

When it comes to debugging JavaScript code in web applications, Browser Developer Tools, such as Chrome DevTools and Firefox Developer Tools, are indispensable. These tools provide a suite of features for inspecting HTML elements, monitoring network requests, and debugging JavaScript code in real-time.

Key features of browser DevTools include:

  • Console: The Console tab allows developers to log messages, execute JavaScript code snippets, and debug runtime errors directly within the browser environment.

  • Debugger: The Debugger tab provides a powerful interface for setting breakpoints, stepping through code execution, and inspecting variable values during runtime. Developers can pinpoint the exact location of errors and trace the execution flow through complex JavaScript functions.

  • Network Analysis: The Network tab enables developers to monitor HTTP requests and responses, analyze network performance, and identify potential bottlenecks or errors in API interactions.

  • DOM Inspection: The Elements tab provides a visual representation of the Document Object Model (DOM), allowing developers to inspect and manipulate HTML elements, CSS styles, and event listeners dynamically.

By leveraging browser DevTools in conjunction with server-side debugging techniques, developers can diagnose and resolve issues more effectively, regardless of whether they originate from frontend or backend code. This integrated approach to debugging empowers developers to gain comprehensive insights into application behavior.

Command Line Interface (CLI) Tools:

Rails developers often rely on command-line tools to streamline development workflows and troubleshoot issues efficiently. Leveraging CLI tools like Pry, Byebug, or Rails console allows developers to interactively explore the application state, execute ad-hoc queries, and simulate edge cases in a controlled environment. Additionally, tools like Rails ERD or Brakeman help analyze database schemas and identify security vulnerabilities, respectively.

Creative Debugging Techniques:

Sometimes, resolving obscure bugs in Rails applications requires thinking outside the box and employing creative debugging techniques. This could involve using logging libraries like Airbrake to capture detailed application logs and trace the execution flow across multiple components. Additionally, implementing feature flags or toggles using tools like Flipper can enable developers to selectively enable or disable specific features in production to isolate and diagnose issues.

Diagnosing Issues in Production Systems:

Debugging issues in production systems requires a different approach due to the constraints of real-world environments. Senior developers often employ strategies such as log aggregation and monitoring using tools like Elasticsearch, Kibana, or New Relic to gain insights into application performance, error rates, and resource utilization. By setting up robust alerting mechanisms and implementing automated recovery procedures, developers can proactively detect and mitigate issues before they impact end-users.

Debugging Production Issues with AWS CloudWatch Logs:

When facing production issues in a Rails application deployed on AWS, leveraging AWS CloudWatch Logs can provide valuable insights into the root cause of the problem. Here’s a concise guide to debugging based on logs alone:

  1. Log Collection Setup: Ensure Rails application logs are configured to capture relevant information. By default, Rails logs to log/production.log.

  2. CloudWatch Logs Configuration: Set up AWS resources to send log data to CloudWatch Logs. Install the CloudWatch Logs agent or configure log streaming from application code.

  3. Log Analysis and Insights: Use the CloudWatch Logs console to search, filter, and analyze log events in real-time. Create metric filters and alarms to detect specific patterns or anomalies.

  4. Debugging Based on Logs Alone: Identify relevant log entries related to the reported problem. Look for error messages, exceptions, or warning signs to trace the issue’s root cause.

  5. Sample Queries: For CloudWatch Logs, a sample query might look like this:

fields @timestamp, @message
| filter @message like /NoMethodError/
| sort @timestamp desc
  1. Sample Rails Log Entry: A sample Rails log entry showing a “NoMethodError” with a larger stack trace might look like this:
[ERROR] [2024-03-10 15:45:21] NoMethodError (undefined method 'name' for nil:NilClass):
Traceback (most recent call last):
  app/controllers/users_controller.rb:25:in `show'
  app/controllers/application_controller.rb:42:in `authorize'
  app/models/user.rb:36:in `get_name'
  app/models/user.rb:15:in `full_name'
  app/views/users/show.html.erb:12:in `_app_views_users_show_html_erb___123456789'
...

Remote Debugging and Error Tracking with Airbrake

In addition to leveraging logs for debugging, utilizing specialized error tracking and monitoring tools like Airbrake can enhance the debugging process further. Airbrake provides real-time error tracking for Rails applications, capturing exceptions and errors as they occur in production environments. By integrating Airbrake with your Rails application, you can gain insights into the frequency, severity, and impact of errors, allowing you to prioritize and address critical issues promptly.

Using Airbrake for Remote Debugging

Airbrake offers remote debugging capabilities, enabling developers to diagnose and troubleshoot errors without direct access to the production environment. When an error occurs, Airbrake captures relevant information such as stack traces, request parameters, and environment details, providing valuable context for identifying the root cause of the issue. Developers can then use the Airbrake dashboard to view and analyze error occurrences, track their resolution progress, and collaborate with team members to implement fixes efficiently.

Benefits of Airbrake Integration

  • Real-time Error Notifications: Receive instant notifications when errors occur in your Rails application, allowing you to respond promptly and minimize downtime.

  • Detailed Error Reports: Access detailed error reports with stack traces, environment information, and user context to gain insights into the circumstances surrounding each error occurrence.

  • Workflow Integration: Seamlessly integrate Airbrake with your existing development workflow, enabling you to triage, prioritize, and address errors alongside feature development and bug fixes.

  • Historical Error Data: Leverage historical error data and trends to identify recurring issues, prioritize bug fixes, and proactively address potential stability and performance issues.

By incorporating Airbrake into your debugging toolkit, you can streamline the debugging process, improve the reliability of your Rails applications, and deliver a better experience for your users.

In conclusion, while documentation serves as a crucial reference point in understanding system architecture and functionality, it’s essential to acknowledge its limitations, especially in complex and evolving systems. The ability of senior developers to navigate through codebases via reverse engineering principles offers a complementary approach to understanding system intricacies. By combining thorough documentation with the adeptness to reverse engineer, developers can gain deeper insights into the inner workings of applications, fostering robust debugging, and problem-solving capabilities. Tools like AWS CloudWatch Logs, Airbrake, and other logging libraries further augment this process by providing real-time visibility into application behavior.

Ultimately, the synergy between documentation and reverse engineering empowers developers to navigate intricate codebases effectively, paving the way for increased productivity and efficiency.

Sprout Method

Write tests for new code even if the old code isn’t under test. Although this isn’t a good long term solution it can help move the design forward.

For simple changes that can be captured in a method we can TDD that.

Let’s say we have some existing code:

public class TransactionGate
{
  public void postEntries(List entries) {
    // iterate through entries list and persist the transaction
  }
}

Now we want to remove duplicate entry’s before persisting them. A quick thing to do is write the new logic directly inside postEntries but this is bad for several reasons such as SRP violation. Instead we can sprout a new method called uniqueEntries and call it inside postEntries


public class TransactionGate
{
  // TDD this new method
  List uniqueEntries(List entries) {
    //remove duplicate entries
  }

  public void postEntries(List entries) {
    List entriesToAdd = uniqueEntries(entries);
    // iterate through entries list and persist the transaction
  }
}

Addendum (3/7/2024):

In response to feedback and to provide further clarity, I’d like to emphasize the importance of understanding the “sprout method” concept and its application in software development.

Understanding the “Sprout Method” Concept:

The “sprout method” pattern involves extracting a new method from existing code to encapsulate specific functionality. This approach promotes code modularity, readability, and maintainability by adhering to the Single Responsibility Principle (SRP). By isolating distinct tasks into separate methods, developers can create more cohesive and reusable code.

Benefits of Using the “Sprout Method” Pattern:

  1. Improved Code Organization: The “sprout method” pattern allows developers to organize code more effectively by grouping related functionality into separate methods. This enhances code readability and makes it easier to understand the purpose and behavior of each method.

  2. Enhanced Testability: Extracting logic into separate methods facilitates unit testing, as it isolates specific behaviors for testing. With focused, well-defined methods, developers can write more targeted tests with clear inputs and expected outputs.

  3. Easier Maintenance and Refactoring: By adhering to the SRP and separating concerns, the “sprout method” pattern simplifies maintenance and refactoring efforts. Developers can modify or extend individual methods without impacting other parts of the codebase, reducing the risk of unintended side effects.

Applying the “Sprout Method” Pattern in Practice:

When faced with the task of adding new functionality or modifying existing code, consider whether the “sprout method” pattern can help improve code structure and maintainability. Look for opportunities to extract cohesive, reusable methods that encapsulate specific behaviors or operations.

In summary, understanding and applying the “sprout method” pattern can lead to cleaner, more maintainable codebases. By leveraging this pattern effectively, developers can enhance code organization, testability, and overall software quality.

Extract Interface

Note: The sample code in this post is from Feathers’ book Working Effectively With Legacy Code which i’m trying to read through and summarize my knowledge.

Sometimes your design shows it could use improvement if you notice it’s hard to test. When it’s hard to test your code, you eventually won’t even bother.

A common thing that occurs is when you try to put a class under test it has dependencies/collaborating classes which need to be created. Below the Sale class depends on a ArtR56Display class.

public class Sale {
  public void scan() {
      ArtR56Display display = new ArtR56Display();
      display.showLine();
    }
  }
}

First, we need to use dependency injection on the display object. One easy way to do this is by passing it through the constructor. Typically instantiating objects inside another class will highlight dependencies you need to overcome (although there are exceptions e.g. basic value model objects).

public class Sale {
  private ArtR56Display display;

  public Sale(ArtR56Display display) {
    this.display = display;
  }

  public void scan() {
      display.showLine();
    }
  }
}

Now we want to test our sale class. We create a test which will instantiate a ArtR56Display but suprise, it’s connected to a physical cash register. It’s not very convenient to write unit tests that rely on this sort of external dependency just to run. Other typical dependencies such as DB connections also suffer from this issue.

So the solution here is to extract the display to an interface which has the side effect of promoting loose coupling and replaceability. It allows the Sale class to be extended but not modified. So in the future our Sale class can be hooked up to any other type of display.

public interface Display
{
  void showLine(String line);
}

public class Sale
{
  private Display display;

  public Sale(Display display) {
    this.display = display;
  }

  public void scan() {
    ...
    String itemLine = item.name() + " " + item.price().asDisplayText();
    display.showLine(itemLine);
  }
}

At this point we could create a FakeDisplay that implements showLine(). In our tests now we can inject our FakeDisplay and still test the scan method to ensure it still sends the right text to the display when the Sale class is used without depending on the physical cash register.

public class FakeDisplay implements Display {
  private String lastLine = "";

  public void showLine(String line) {
    lastLine = line;
  }

  public String getLastLine() {
    return lastLine;
  }
}

As a side note most Mocking libraries can handle most of this stuff for us by creating a proxy class for any interfaces we would like to fake and configuring it to execute dummy behavior.