Alex Stepanov writes in Notes on Programming:
We often get the idea that a mathematical theory is built in a logical way starting from definitions and axioms. This is not the case. The definitions and axioms appear at the very end of the development of a good theory. It invariably starts with simple facts that later on are generalized into theorems, and only at the very end the formal definitions and axioms are developed.
In an effort to become a better programmer (while at the same time rewriting one of my software programs,) we’ll look at a piece of web application code, that I wrote a few years ago. The language used in these examples is Perl.
The idea is to find a better abstraction, to make it easier to add and change code related to the guestbook (and other controllers), while at the same time finding the underlying abstraction that isn’t yet obvious from the code.
The piece of code in question is the list
function of the Guestbook
controller. It lists a some entries limited by a constant. The function loads
the entries from the database using the guestbook_load_entries
function.
The webserver and a few other classes parse the url /guestbook/list
and
dispatch the request to this function. Inside the function we validate all
parameters that are needed.
package WebWinkel::Controller::Guestbook;
use strict;
use base qw/WebWinkel::Controller/;
use WebWinkel::DB::Guestbook 'guestbook_load_entries';
sub list {
my $self = shift;
my $page = $self->validate(-as_integer => 'page');
my $entries = guestbook_load_entries($page, 10);
return $self->render_template('guestbook/list', {
page => $page, entries => $entries
});
}
Each of the lines in the function performs a small part of the whole action. I don’t think it’s important to look too much at the syntax. We’re interested in the structure of the action.
The first line in the function validates the page
parameter. The
guestbook_load_entries
function retrieves a list of guestbook entries from
the database in the second line. In the third line the controller renders a
specific template with the parameters we retrieved from the database.
It seems the structure of the process is quite simple.
- Validate the query parameters,
- Load some database entities using the validated parameters,
- Render a template using the database entities.
There are no branches or loops in this piece of code on this level. The template hides the loops we need for rendering a list.
We can simplify the process to a simple graph.
(1) { Validate → Load → Render }
We omit the specifics in this graph, because we’re looking for an abstraction.
Each node in this graph depends on the values provided by the previous step.
The Validate
step depends on data outside the controller.
Now it’s time to look if this model we just created, can be applied to other controller and their actions. Let’s take a look and see if we can find at least one action that satisfies this model.
package WebWinkel::Controller::Orders;
use strict;
use WebWinkel::DB::Orders qw/orders_find_all_open/;
use Date::Simple qw/today date/;
use base 'WebWinkel::Controller';
sub list {
my ($self) = @_;
my ($orders, $payed) = orders_find_all_open();
my $today = today();
return $self->render_template('orders/list', {
today => $today->format("%d-%m-%Y"),
orders => $orders,
payed_orders => $payed,
});
}
I found this piece of code in the Orders
controller. Let’s see which steps
this function contains.
It starts with the retrieval of the open orders from the database on the first line. The second line gets the current date. The third line renders the template with the database gathered in the previous two lines. The structure of the function look like this.
{ Load → GatherData → Render }
This shows us that our previous model doesn’t completely describe all
controllers and actions. In this instance the GatherData
step doesn’t depend
on the Load
step. We could switch the two statements and the structure will
still be the same. This means we should rewrite this model to include this new
piece information. We’ll use the $
to describe a relation between two steps
where the first doesn’t depend on the second and vice versa. The model now
looks like this.
{ { Load $ GatherData } → Render }
The →
between the first two steps and Render
remains, because all data found
in those steps is used in that Step
. This model can also be written as follows
{ { GatherData $ Load } → Render }
because we declare the operator $
as being commutative. We can’t yet know if
this is an important property of our model, but it describes the examples
better. The model is still quite different from the other model.
How could we combine the two models while still being specific about the steps?
What have the steps Validate
and Load
in common? If we look at the two
code examples we see that both steps create information that is used in a later
step. A later step doesn’t need to be the next step. The step GatherData
also
satisfies this property.
{ { { Validate → Load } $ GatherData } → Render }
The variables found by Validate
are passed into Load
, but not specifically
into GatherData
. How can I say that this model is the same as model (1)?
Let’s say step Validate
creates zero or more pieces of information. If a step
creates zero pieces of information for the next step, then this is same as not
performing the step at all, at least with the current understanding of the
model.
This concludes our first look at the structure of controllers and actions. From experience I know that there are more ways to write a controller. We will leave those for another time, just like the specifics about finding out which functions to call. Maybe we can discover a pattern there as well?