"Give me a lever long enough and a fulcrum on which to place it, and I shall move the world." This is a famous quote by Archimedes. Many evidences indicate that even if he had a fulcrum, it would still take thousands of years to lift the world. Not to mention, where would he find a lever of unimaginable length? But in this article, I'm not discussing the accuracy of the quote. What I find fascinating is his way of thinking: "Give me...I will..."
"Give me x, I will return y" is an input-output thinking in programming. It is a very common problem-solving mindset. Instead of immediately writing code, take the time to evaluate and identify where the approach "give me x, I will return y" can be applied. You may have heard of it in the article Just Code, or Focus on Developing Your Programming Mindset?.
For ease of imagination, let's take an example of a simple shopping feature. Its logic looks like this:
function createOrder(product, quantity, customer) {
// find product information
const productInfo = ProductModel.findOne({
where: {
product_id: product,
}
});
if (!productInfo) {
throw new Error('Product not found');
}
// check quantity in stock
if (productInfo.quantity < 1) {
throw new Error('Not enough product in stock');
}
// create order
const order = OrderModel.create({
customer: customer,
product_id: product,
quantity: quantity,
total_price: productInfo.price * quantity,
});
return order;
}
If you think in terms of input-output, you can make the above code look more concise and reusable. To achieve that, think about separating the logic into smaller pieces, making them Pure functions if possible. If you don't know what a "pure function" is, you can read the article Pure Functions in Javascript. Why Should We Know It as Soon as Possible?.
In the example above, we see three distinct and well-defined logics: finding product information, checking quantity in stock, and creating an order. From there, we can extract them into 3 smaller functions.
Give me a product ID, I will return the product information.
function getProductInfo(product) {
return ProductModel.findOne({
where: {
product_id: product,
},
});
}
Give me the product information, I will check if it is in stock.
function checkProduct(productInfo) {
return productInfo.quantity > 0;
}
Give me the necessary information to create an order, I will create and return the order.
function createOrder(productInfo, customer) {
return OrderModel.create({
customer: customer,
product_id: productInfo.id,
total_price: productInfo.price,
});
}
With these 3 functions serving clear purposes, you can easily reuse them. Anytime you need to find product information, use getProductInfo
. Anytime you need to check quantity in stock, use checkProduct
. Someone may ask why checkProduct
needs to take productInfo
as an argument instead of just the quantity
attribute. The answer to this question depends on your experience in the project. By receiving the entire productInfo
, it increases the ability to expand in the future. For example, if later on there is additional logic that only allows creating orders for products created after one week.
function checkProduct(productInfo) {
return productInfo.quantity > 0 && productInfo.created_at < moment().subtract(7, "d").toISOString();
}
Of course, you can continue to break down checkProduct
into even smaller functions if deemed necessary.
function checkProduct(productInfo) {
return checkQuantity(productInfo.quantity) && checkDate(productInfo.created_at);
}
The benefit of writing input-output functions is that they are very easy to create unit tests. Since they have no dependencies (such as using a variable outside the function), you can write test cases to validate their correctness. For example, to test if checkProduct
works as expected, simply pass in productInfo
with quantity
and created_at
attributes, and the function will return true/false. With one input corresponds to one output, you don't have to worry about whether the function utilizes variables outside the function or has any side effects.
To sum up, thinking in terms of input-output, along with the ability to create Pure functions and limit side effects, allows us to write reusable and easily testable features.
Until now, Archimedes' quote has not been substantiated whether he could lift the earth given all the necessary conditions. But the functions you write can certainly be easily validated for correctness.
There are several ways to help you practice thinking in terms of input-output. After reading the suggestions below, the important thing is to practice by applying them in your personal projects or projects you are involved in.
Firstly, take the time to learn about what Pure functions are? What benefits do they bring?
Secondly, before starting anything, take the time to evaluate the feature you are about to implement. I have an article about the 4 steps to hone your skills at Just Code, or Focus on Developing Your Programming Mindset?
While writing code, do it naturally. Then, review if there are any logics that can be separated. If possible, separate them into Pure functions.
Repeat the above steps, and gradually you will develop the input-output thinking. Later on, when preparing to work on a new feature or a feature similar to a previous one, this thinking will come back to help you save time in evaluation and coding.
Thinking in terms of input-output is not new, you may have used it before without noticing. Hopefully, this article helps you realize the importance of this thinking and enables you to write code more efficiently and effectively.
Me & the desire to "play with words"
Have you tried writing? And then failed or not satisfied? At 2coffee.dev we have had a hard time with writing. Don't be discouraged, because now we have a way to help you. Click to become a member now!
Subscribe to receive new article notifications
Comments (0)