What and how
What makes a piece of code easy to read? Suppose you have a piece of code, say, a function. And you see this for the first time. What makes it easily readable?
I think the main readability characteristic is the extent to which the code conveys its intent. What does it want to achieve? We can make this a little bit more specific: the more the code expresses its intent (and omits details), the better the readability becomes.
Read more: What and howWhat exactly is it that makes the intent clear from the code?
What and how in functions
Here is an example pseudocode for doing groceries. I’ll format it as C#:
void BuyGroceries(Shop shop) {
var shoppingList = MakeShoppingList();
mCar.DriveTo(shop);
mCar.Park(); // those self-driving cars are awesome!
var cart = shop.GetShoppingCart();
foreach(var item in shoppingList.GetRemainingItems()) {
var instance = shop.GetItem(item);
if(instance != null) {
cart.Add(instance);
shoppingList.RemoveItem(item);
}
}
shop.Checkout(cart);
mCar.Load(cart.GetContents());
mCar.DriveTo(mHome);
}
The above listing is very detailed, even though the method itself is fairly simple. The essence is buried in the details, so you can only understand the intention if you’ve read the whole listing.
If you read this, you’re probably analyzing which chunks can be made from this listing. In other words, you’re trying to separate the essence from the details. Note that every developer has to do this in order to get an understanding of the code. We can make their life easier by organizing the code as such. The first step can be to extract a method to do the actual shopping:
void BuyGroceries(Shop shop) {
var shoppingList = MakeShoppingList();
mCar.DriveTo(shop);
mCar.Park();
var boughtItems = BuyItems(shop, shoppingList);
mCar.Load(bouthItems);
mCar.DriveTo(mHome);
}
private ICollection<Item> BuyItems(Shop shop, ShoppingList list) {
var cart = shop.GetShoppingCart();
foreach(var item in list.GetRemainingItems()) {
var instance = shop.GetItem(item);
if(instance != null) {
cart.Add(instance);
shoppingList.RemoveItem(item);
}
}
shop.Checkout(cart);
return cart.GetContents();
}
Extracting a method basically means that you assign a name to a block of statements. The method name should tell what the method must accomplish, and the assumption is that the statements inside the method together achieve this goal.
But by extracting a method, we have also made a logical separation: the method name says what must be done, while the method contents state how it is done. So basically we made a separation between what and how.
What and how in polymorphism
But functions are not the only way to separate what and how. We can make such a separation also on class level using polymorphism.
The example below uses the command pattern to illustrate this. The core part of this pattern is an interface like this:
public interface ICommand {
void Execute();
}
This interface states what implementations must do, but there is no sign of how they must do this. This leaves the freedom to create any implementation we like. For example:
public class SendEmailCommand : ICommand {
...
public void Execute() {
using var client = new SmtpClient(mHost);
client.Send(mSender, mRecipients, mSubject, mBody);
}
}
or
public class OpenFileCommand : ICommand {
...
public void Execute() {
Process.Start(mFile);
}
}
Either implementation can be passed around as an ICommand, and the client does not need to know what the actual implementation is, just that it is a command that can be executed.
So here again: the interface defines the what, and the implementations define the various cases of how. Only this time, the how can be varied over according to what the developer needs.
The above two classes that implement ICommand are not fully shown. What should be on the place of the dots? In the first example of SendEmailCommand, the implementation requires email specific data: sender, recipients, subject and body. This data cannot be passed to Execute, because that would require the consumers to pass email specific data, resulting in tight coupling (the user of ICommand must then know that at least some implementation sends emails and it must be provided with the correct parameters).
This leaves the constructor as the primary candidate to pass email specific information to SendEmailCommand. This also means that the SendEmailConstructor exposes quite a bit of knowledge about the implementation:
public class SendEmailCommand : ICommand {
private readonly string mHost;
private readonly string mSender;
private readonly string mRecipients;
private readonly string mSubject;
private readonly string mBody;
public SendEmailCommand(
string host,
string sender,
string recipients,
string subject,
string body
) {
mHost = host;
mSender = sender;
mRecipients = recipients;
mSubject = subject;
mBody = body;
}
public void Execute() {
using var client = new SmtpClient(mHost);
client.Send(mSender, mRecipients, mSubject, mBody);
}
}
This shows just one example implementation; other implementations are also possible. For example, we can envision an implementation that receives some email factory, so we can create the message parameters more dynamically. But this fact is then still exposed from the constructor.
So this means that the constructor is part of the how, not the what. But the public implementation is part of the what.
hat and how can also be defined from a different angle: what is the observable behavior, while how is the technical implementation. The observable behavior is the behavior that you can see from outside the implementation.
What, how and abstractions
Also note that what and how are respective to some abstraction layer. The what of a higher level layer is the how of the layer below.
public void Do() {
Foo();
Bar();
Baz();
}
The calls to Foo(), Bar() and Baz() are how Do() achieves its function. But Foo(), Bar() and Baz() are the what of the abstraction layer below. They have an implementation of their own.