Backward Compatibility: Practice

Welcome back! Today, we'll master what we learned about backward compatibility in practice. Get prepared to apply all the knowledge to practice tasks, but first, let's look at two examples and analyze them.

Task 1: Enhancing a Complex Data Processing Function with Default Parameters

Let's say that initially, we have a complex data processing function designed to operate on an array of objects, applying a transformation that converts all string values within the objects to uppercase. Here's the initial version:

function processData(items) {
    const processedItems = items.map(item => {
        const processedItem = {};
        for (const key in item) {
            processedItem[key] = typeof item[key] === 'string' ? item[key].toUpperCase() : item[key];
        }
        return processedItem;
    });
    
    processedItems.slice(0, 3).forEach(item => console.log(`Processed Item: ${JSON.stringify(item)}`));
}

// Default behavior - convert string values to uppercase
processData([{ name: "apple", quantity: 10 }, { name: "orange", quantity: 5 }]);

We intend to expand this function, adding capabilities to filter the items based on a condition and allow for custom transformations. The aim is to retain backward compatibility while introducing these enhancements. Here's the updated approach:

function processData(items, condition = () => true, transform = null) {
    const processedItems = items.reduce((acc, item) => {
        if (condition(item)) {
            let processedItem;
            if (transform) {
                processedItem = transform(item);
            } else {
                processedItem = {};
                for (const key in item) {
                    processedItem[key] = typeof item[key] === 'string' ? item[key].toUpperCase() : item[key];
                }
            }
            acc.push(processedItem);
        }
        return acc;
    }, []);

    processedItems.slice(0, 3).forEach(item => console.log(`Processed Item: ${JSON.stringify(item)}`));
}

// Default behavior - convert string values to uppercase
processData([{ name: "apple", quantity: 10 }, { name: "orange", quantity: 5 }]);

// Custom filter - select items with a quantity greater than 5
processData(
    [{ name: "apple", quantity: 10 }, { name: "orange", quantity: 5 }],
    item => item.quantity > 5
);

// Custom transformation - convert names to uppercase and multiply the quantity by 2
const customTransform = item => {
    const transformedItem = {};
    for (const key in item) {
        transformedItem[key] = key === "name" ? item[key].toUpperCase() : item[key] * 2;
    }
    return transformedItem;
};
processData(
    [{ name: "apple", quantity: 10 }, { name: "orange", quantity: 5 }],
    () => true,
    customTransform
);

In the evolved version, we've introduced two optional parameters: condition, an arrow function to filter the input array based on a given condition, and transform, an arrow function for custom transformations of the filtered items. The default behavior processes all items, converting string values to uppercase, which ensures that the original function's behavior is maintained for existing code paths. This robust enhancement strategy facilitates adding new features to a function with significant complexity while preserving backward compatibility, showcasing an advanced application of evolving software capabilities responsively and responsibly.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal