Lesson 3
Long Parameter List: Introduce Parameter Object
Introduction and Overview

Welcome to the third lesson of our course, where we dive into the concept of Parameter Object to tackle a common code smell: complex function signatures. Throughout this course, we'll work on eliminating code smells to enhance the readability, maintainability, and scalability of your codebase, with a strong emphasis on Test Driven Development (TDD).

In our previous lessons, we addressed code smells like duplicated code using the Extract Method technique to manage long methods. Today, we'll confront the code smell associated with long parameter lists and introduce the Parameter Object as an effective solution. We'll be leveraging TypeScript's powerful type-checking capabilities and using Jest for testing. While TypeScript is our language of choice, similar solutions are available in JavaScript and other languages.

Understanding and mastering the TDD cycle — Red, Green, Refactor — will be critical as we work to eliminate code smells in this lesson.

Why Long Parameter Lists are a Code Smell

Long parameter lists are considered a code smell because they complicate function signatures, making the code harder to read, maintain, and test. This complexity arises from several issues:

  1. Readability: When a function has too many parameters, it's challenging to understand what each parameter represents without referring to the documentation or function implementation. It clutters the function definition and makes the code less intuitive.

  2. Maintainability: Modifying a function with a long parameter list becomes cumbersome. Adding or removing parameters can lead to errors in existing function calls across the codebase, increasing the risk of bugs.

  3. Error-Prone: It's easy to mix up or misorder parameters, especially when their types are similar. Even with type-checking, logical errors can occur if parameters are passed in the wrong order or are misunderstood.

  4. Testing Challenges: Long parameter lists make writing and maintaining tests more difficult, as test cases need to supply many arguments. This complexity can discourage thorough testing and make tests brittle against changes.

  5. Lack of Cohesion: A long list of parameters may indicate that not all parameters are related or that the function is doing too much, violating the single responsibility principle.

By recognizing long parameter lists as a code smell and refactoring them into a Parameter Object, we can improve how we work with and maintain our code, making it more robust and easier to understand.

Example: Long Parameter List

Let's start by identifying the problematic long parameter list in the existing code. Consider the processExamScore function in exam-processor.ts. The function has numerous parameters, making it difficult to read and error-prone when changes are made.

TypeScript
1export function processExamScore( 2 examScore: number, 3 isHomeworkComplete: boolean, 4 attendanceScore: number, 5 bonusActivities: BonusActivity[], 6 examWeight: number, 7 homeworkWeight: number, 8 attendanceWeight: number, 9 bonusPointsPerActivity: number, 10 passingThreshold: number, 11 isRetake: boolean, 12 courseCode: string 13): ExamResult { 14 // Function implementation... 15}

This complexity can lead to various issues such as incorrectly ordered parameters during function calls and increased difficulty when refactoring or adding new features.

Confusing Tests

Additionally, these long parameter lists make the testing process cumbersome, as seen in exam-processor.test.ts.

TypeScript
1test('calculates maximum score with completed homework', () => { 2 const result = processExamScore( 3 100, 4 true, // completed homework 5 3, 6 [], 7 0.7, 8 0.2, 9 0.1, 10 2, 11 75, 12 false, 13 'MATH101' 14 ); 15 16 expect(result.finalScore).toBe(100); 17});

Notice how difficult it is to read this test! What do the values 0.7, 0.1, 2, and 75 even mean?

Refactor: Introduce Paramater Objects and Defaults
TypeScript
1export function processExamScore( 2 courseCode: string, 3 4 // Individual Performance 5 examScore: number, 6 isHomeworkComplete: boolean = true, 7 attendanceScore: number = 3, 8 bonusActivities: BonusActivity[] = [], 9 isRetake: boolean = false, 10 11 // Configuration 12 scoreWeights: ScoreWeights = defaultScoreWeights, 13 coursePolicy: CoursePolicy = defaultCoursePolicy, 14): ExamResult { 15 // Implementation 16}

Notice the following:

  • Re-ordering parameters: Parameters have been reorganized in the function signature to group related values, enhancing readability and logical flow. For instance, parameters related to the course information are grouped together, separate from those related to individual performance or scoring configurations.

  • Parameter Objects: The function has been refactored to utilize parameter objects, which encapsulate multiple related parameters into a single cohesive object. This reduces complexity in the function signature and clarifies its intent.

  • Defaults: Default values have been assigned to certain parameters, such as isHomeworkComplete, and attendanceScore, to provide sensible defaults when those values are not explicitly specified, simplifying function calls and reducing potential errors.

Less Confusing Tests

The refactored test example below demonstrates improved readability by eliminating the need to decipher multiple unordered parameters:

TypeScript
1test('calculates maximum score with completed homework', () => { 2 const result = processExamScore('MATH101', 100); 3 4 expect(result.finalScore).toBe(100); 5});

This clarity is achieved by utilizing a simplified function call, where essential parameters like courseCode and examScore are explicitly specified, making it immediately clear what values are being tested. This concise form minimizes cognitive load, reduces potential errors related to parameter misordering, and increases maintainability, ultimately making the test more intuitive and focused on its intent.

Real-world Application

Parameter Objects are exceptionally useful in real-world scenarios where functions require multiple related parameters. For example, consider an e-commerce platform where you handle payment and shipping details through parameter objects, simplifying both function calls and testing.

However, it's crucial to acknowledge scenarios where alternatives like optional parameters or builder patterns might be more effective, especially if parameters don't naturally form a cohesive group.

Summary and Practice Preparation

In this lesson, we've learned to refactor long parameter lists by introducing Parameter Objects:

  • Recognized issues with long parameter lists.
  • Created a Parameter Object to bundle related parameters, simplifying function signatures.
  • Re-organized the parameters for clarity
  • Introduced defaults
  • Utilized TypeScript interfaces for type-checking and organization.
  • Updated tests to accommodate the refactored design.

Get ready to apply these techniques in the upcoming exercises, where you'll enhance your skills in refactoring and testing with Parameter Objects. These exercises will enable you to solidify your understanding of the TDD workflow — Red, Green, Refactor — as you work toward maintaining a robust and scalable codebase.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.