Last modified: Jun 25, 2014
Stepwise Refinement
Stepwise refinement is a basic technique for low-level design.
Invented by Niklaus Wirth in 1971, it may be the oldest systematic approach to software design still in use. But it remains quite relevant to modern programming.
Stepwise refinement is a discipline of taking small, easily defended steps from a very generic view of an algorithm, adding a few details at each step, until the path to an actual program becomes clear.
It’s a great answer to that awful, hollow feeling that you may get when staring at a blank sheet of paper or editor window and wondering, “how in the world am I supposed come up with a program to do …?”
Fundamental to the process of stepwise refinement is pseudocode, a low-level design tool or notation hat allows us the flexibility to write about algorithms that we don’t entirely understand yet.
Pseudo-Code
Also known as “structured English”
(at least, in English-speaking countries)
, pseudocode uses
Sequence – one thing after another
Selection – if
Iteration – while, for
natural language (English) descriptions of functions and data
program-style indentation and \{~\}for grouping and readability
Pseudo-Code Example
void primaryElection() {
read all candidates;
read # of states conducting primaries;
for each such state {
read votes cast in this state;
assign delegates to candidates;
}
for each candidate c {
print report for candidate(c);
}
}
Stepwise Refinement is Stepwise
The heart of stepwise refinement is the idea that
pseudo code statements are repeatedly expanded
Procedure
Start with a general statement of the problem.
Pick any statement whose translation into code is non-obvious.
Expand that statement into 2 or more steps.
Repeat steps 2–4 until all statements can be easily coded.
The essential elements here are that you
Expand one statement at a time
and the replacements must, together, account for the whole statement being replaced
Example: The Auction Program
Start with a general statement:
//Determine winners of daily auction;
Pick a statement that needs expansion. (OK, there’s only one choice.)
// Determine winners of daily auction;
//* initialize auction data;
//* determine Winners;
//* clean up;
The first expansion is kind of easy. Almost any algoithm can safely be said to start with initialization, end with some kind of cleanup, with all the hard work happening in between.
But there’s nothing wrong with easy steps. In fact, small easy steps are preferred because you can usually convince yourself more easily that you have not made a mis-step and forced yourself into a direction where you will never be able to complete a working design.
Although not absolutely necessary, I recommend writing your pseudocode as comments.
The *’s are a convention to help indicate the depth of the expansion.
So these new statements are all level–1, and we can see at a glance which statement they must be an expansion of.
Determining the winners
Again, pick a statement that needs expansion
// Determine winners of daily auction;
//* initialize auction data;
//* determine Winners;
//** for each item {
//** resolve the auction for item;
//**}
//* clean up;
We could have picked any of the 3 level–1 statements. But I had a pretty good idea that determining the winners means that we needed to determine a winner for each item.
Initializing the auction data
// Determine winners of daily auction;
//* initialize auction data;
//** read items;
//** read bidders;
//** read bids;
//* determine Winners;
//** for each item {
//** resolve the auction for item;
//** }
//* clean up;
This is a pretty safe bet, given the info provided in the auction program description.
It should be a bit more apparent, now, how the * convention aids us in keeping track of which statements are expansions of which other statements.
Resolving the auction for one item
// Determine winners of daily auction;
//* initialize auction data;
//** read items;
//** read bidders;
//** read bids;
//* determine Winners;
//** for each item {
//** resolve auction for item;
//*** get winning bid for this item;
//*** if any bid is a winner {
//*** print winning bid;
//*** subtract bid amount from winners account;
//*** } else {
//*** print notice of no winner;
//*** }
//** }
//* clean up;
Getting the winning bid
// Determine winners of daily auction;
//* initialize auction data;
//** read items;
//** read bidders;
//** read bids;
//* determine Winners;
//** for each item {
//** resolve auction for item;
//*** get winning bid for this item;
//**** highestBid = 0;
//**** for each bid for this item {
//**** if (bid is the best so far) {
//**** remember this bid as highest so far;
//**** }
//*** }
//*** if any bid is a winner {
//*** print winning bid;
//*** subtract bid amount from winners account;
//*** } else {
//*** print notice of no winner;
//*** }
//** }
//* clean up;
Best bid so far
// Determine winners of daily auction;
//* initialize auction data;
//** read items;
//** read bidders;
//** read bids;
//* determine Winners;
//** for each item {
//** resolve auction for item;
//*** get winning bid for this item;
//**** highestBid = 0;
//**** for each bid for this item {
//**** if (bid is the best so far) {
//***** if (bid is in time &&
//***** bid is higher than highestBid &&
//***** bidder has enough money) {
//**** remember this bid as highest so far;
//**** }
//*** }
//*** if any bid is a winner {
//*** print winning bid;
//*** subtract bid amount from winners account;
//*** } else {
//*** print notice of no winner;
//*** }
//** }
//* clean up;
Reading items
// Determine winners of daily auction;
//* initialize auction data;
//** read items;
//*** read #items;
//*** for (int i = 0; i < #items; ++i)
//*** read item[i];
//** read bidders;
//** read bids;
//* determine Winners;
//** for each item {
//** resolve auction for item;
//*** get winning bid for this item;
//**** highestBid = 0;
//**** for each bid for this item {
//**** if (bid is the best so far) {
//***** if (bid is in time &&
//***** bid is higher than highestBid &&
//***** bidder has enough money) {
//**** remember this bid as highest so far;
//**** }
//*** }
//*** if any bid is a winner {
//*** print winning bid;
//*** subtract bid amount from winners account;
//*** } else {
//*** print notice of no winner;
//*** }
//** }
//* clean up;
Read bidders
// Determine winners of daily auction;
//* initialize auction data;
//** read items;
//*** read #items;
//*** for (int i = 0; i < #items; ++i)
//*** read item[i];
//** read bidders;
//*** read #bidders;
//*** for (int i = 0; i < #bidders; ++i)
//*** read bidder[i];
//** read bids;
//* determine Winners;
//** for each item {
//** resolve auction for item;
//*** get winning bid for this item;
//**** highestBid = 0;
//**** for each bid for this item {
//**** if (bid is the best so far) {
//***** if (bid is in time &&
//***** bid is higher than highestBid &&
//***** bidder has enough money) {
//**** remember this bid as highest so far;
//**** }
//*** }
//*** if any bid is a winner {
//*** print winning bid;
//*** subtract bid amount from winners account;
//*** } else {
//*** print notice of no winner;
//*** }
//** }
//* clean up;
Read bids
// Determine winners of daily auction;
//* initialize auction data;
//** read items;
//*** read #items;
//*** for (int i = 0; i < #items; ++i)
//*** read item[i];
//** read bidders;
//*** read #bidders;
//*** for (int i = 0; i < #bidders; ++i)
//*** read bidder[i];
//** read bids;
//*** read #bids;
//*** for (int i = 0; i < #bids; ++i)
//*** read bid[i];
//* determine Winners;
//** for each item {
//** resolve auction for item;
//*** get winning bid for this item;
//**** highestBid = 0;
//**** for each bid for this item {
//**** if (bid is the best so far) {
//***** if (bid is in time &&
//***** bid is higher than highestBid &&
//***** bidder has enough money) {
//**** remember this bid as highest so far;
//**** }
//*** }
//*** if any bid is a winner {
//*** print winning bid;
//*** subtract bid amount from winners account;
//*** } else {
//*** print notice of no winner;
//*** }
//** }
//* clean up;
Remembering Bids
// Determine winners of daily auction; //* initialize auction data; //** read items; //*** read #items; //*** for (int i = 0; i < #items; ++i) //*** read item[i]; //** read bidders; //*** read #bidders; //*** for (int i = 0; i < #bidders; ++i) //*** read bidder[i]; //** read bids; //*** read #bids; //*** for (int i = 0; i < #bids; ++i) //*** read bid[i]; //* determine Winners; //** for each item { //** resolve auction for item; //*** get winning bid for this item; //**** highestBid = 0; //**** winnerSoFar = ""; //**** for each bid for this item { //**** if (bid is the best so far) { //***** if (bid is in time && //***** bid is higher than highestBid && //***** bidder has enough money) { //**** remember this bid as highest so far; //***** highestBid = amount of this bid; //***** winnerSoFar = person who made this bid; //**** remember this bid as highest so far; //**** } //*** } //*** if any bid is a winner { //*** print winning bid; //*** subtract bid amount from winners account; //*** } else { //*** print notice of no winner; //*** } //** } //* clean up;
Are We Done Yet?
Depends partly on the individual
Are there any questions about how you would code the rest of this?
Top-Down Design
Example: Top-Down Auction
Top-down starts like ordinary stepwise refinement:
Start with a general statement:
// Determine winners of daily auction;
Pick a statement that needs expansion
// Determine winners of daily auction;
//* initialize auction data;
//* determine Winners;
//* clean up;
But now we treat this as an indication that we will want 3 functions:
Pseudocode statements \(\Rightarrow\) Function calls
int main (int argc, char** argv) {
// Determine winners of daily auction;
initializeAuctionData();
determineWinners();
cleanUp();
return 0;
}
Pick one and design it, top-down.
Determining Winners (stepwise)
In stepwise refinement, we would have gone from
// Determine winners of daily auction;
//* initialize auction data;
//* determine Winners;
//* clean up;
to
// Determine winners of daily auction;
//* initialize auction data;
//* determine Winners;
//** for each item {
//** resolveAuction(item);
//** }
//* clean up;
Determining Winners (top-down)
In top-down, we instead go from
int main (int argc, char** argv) {
// Determine winners of daily auction;
initializeAuctionData();
determineWinners();
cleanUp();
return 0;
}
to
int main (int argc, char** argv) {
// Determine winners of daily auction;
initializeAuctionData();
determineWinners();
cleanUp();
return 0;
}
void determineWinners() {
for each item {
resolveAuction(item);
}
}
Now we might further infer that we will want a resolveAuction function.
Initializing Auction Data
Pick one of the unexpanded functions (initializeAuctionData, cleanUp, or resolveAuction) to design.
void initializeAuctionData() {
// read items;
// read bidders;
// read bids;
}
Do we make each of these 3 lines into 3 more functions?
A judgment call.
which should proceed pretty much as it did in the stepwise example
Continuing Top-Down
int main (int argc, char** argv) {
// Determine winners of daily auction;
initializeAuctionData();
determineWinners();
cleanUp();
return 0;
}
void determineWinners() {
for each item {
resolveAuction(item);
}
}
void initializeAuctionData() {
// read items;
// read bidders;
// read bids;
}
Let’s look at resolving the auction.
Resolving the auction
void resolveAuction(item) {
// get winning bid for this item;
// if any bid is a winner {
// print winning bid;
// subtract bid amount from winners account;
// } else {
// print notice of no winner;
// }
}
Do we make any of these lines into a separate function?
A judgment call.
getWinningBid
void getWinningBid (item) {
// highestBid = 0;
// for each bid for this item {
// if (bid is the best so far) {
// remember this bid as highest so far;
// }
// }
}
This calls for some internal stepwise refinement to add missing detail.
Refining getWinningBid
void getWinningBid (item) {
// highestBid = 0;
// for each bid for this item {
// if (bid is the best so far) {
//* if (bid is in time &&
//* bid is higher than highestBid &&
//* bidder has enough money) {
// remember this bid as highest so far;
// }
// }
}
Further refining getWinningBid
void getWinningBid (item) { // highestBid = 0; //* winnerSoFar = ""; // for each bid for this item { // if (bid is the best so far) { //* if (bid is in time && //* bid is higher than highestBid && //* bidder has enough money) { // remember this bid as highest so far; //* highestBid = amount of this bid; //* winnerSoFar = person who made this bid; // } // }
Any more separate functions?
noLaterThan
void noLaterThan (time1, time2) {
if time1 hours < time2 hours
return true;
if time1 hours > time2 hours
return false;
if time1 minutes < time2 minutes
return true;
if time1 minutes > time2 minutes
return false;
return (time1 seconds <= time2 seconds);
}
Recap
Functions introduced so far:
main, initializeAuctionData, determineWinners, cleanUp, resolveAuction, readItems, readBidders, readBids, getWinningBid, noLaterThan
Overall:
void noLaterThan (time1, time2) {
// if time1 hours < time2 hours
// return true;
// if time1 hours > time2 hours
// return false;
//
// if time1 minutes < time2 minutes
// return true;
// if time1 minutes > time2 minutes
// return false;
//
// return (time1 seconds <= time2 seconds);
}
void getWinningBid (item) {
highestBid = 0;
winnerSoFar = "";
// for each bid for this item {
// if (bid is the best so far) {
//* if (bid is in time &&
//* bid is higher than highestBid &&
//* bidder has enough money) {
// remember this bid as highest so far;
//* highestBid = amount of this bid;
//* winnerSoFar = person who made this bid;
}
}
}
void resolveAuction(item) {
// get winning bid for this item;
// if any bid is a winner {
// print winning bid;
// subtract bid amount from winners account;
// } else {
// print notice of no winner;
// }
}
void initializeAuctionData() {
// read items;
// read bidders;
// read bids;
}
void determineWinners() {
// for each item {
// resolveAuction(item);
// }
}
int main (int argc, char** argv) {
// Determine winners of daily auction;
initializeAuctionData();
determineWinners();
cleanUp();
return 0;
}
#include <iostream>
#include <string>
using namespace std;
//void noLaterThan (time1, time2) {
bool noLaterThan (int h1, int m1, int s1,
int h2, int m2, int s2) {
// if time1 hours < time2 hours
if (h1 < h2)
return true;
// if time1 hours > time2 hours
if (h1 > h2)
return false;
//
// if time1 minutes < time2 minutes
if (m1 < m2)
return true;
// if time1 minutes > time2 minutes
if (m1 > m2)
return false;
//
// return (time1 seconds <= time2 seconds);
return (s1 <= s2);
}
//void getWinningBid (item) {
void getWinningBid (int itemNum, double& highestBid, string& winningBidder) {
highestBid = 0;
winningBidder = "";
// for each bid for this item {
for (int bidNum = 0; bidNum < nBids; ++bidNum) {
if (bidItems[bidNum] == itemNames[itemNum]) {
// if (bid is the best so far) {
//* if (bid is in time &&
if (noLaterThan(bidHours[bidNum], bidMinutes[bidNum],
bidSeconds[bidNum],
itemEndHours[itemNum], itemEndMinutes[itemNum],
itemEndSeconds[itemNum]) &&
//* bid is higher than highestBid &&
bidAmounts[bidNum] > highestBid &&
//* bidder has enough money) {
bidAmounts[bidAmounts] <=
bidderBalances[findBidder(bidBidder[bidNum])]) {
// remember this bid as highest so far;
//* highestBid = amount of this bid;
highestBid = bidAmounts[bidNum];
//* winnerSoFar = person who made this bid;
winningBidder = bidBidder[bidNum];
}
}
}
}
// void resolveAuction(item) {
void resolveAuction(int itemNum) {
// get winning bid for this item;
int highestBidAmount;
string winningBidder;
getWinningBid (itemNum, highestBidAmount, winningBidder);
// if any bid is a winner {
if (highestBidAmount > 0)
{
// print winning bid;
cout << itemNames[itemNum]
<< " won by " << winningBidder
<< " for " << highestBidAmount << endl;
// subtract bid amount from winners account;
int bidderNum = findBidder(winningBidder);
bidderBalances[bidderNum] -= highestBidAmount;
}
else
{
// print notice of no winner;
cout << itemNames[itemNum]
<< " reserve not met"
<< endl;
}
}
void initializeAuctionData(string itemsFileName,
string biddersFileName,
string bidFileName)
{
// read items
readItems(itemsFileName);
// read bidders
readBidders(biddersFileName);
// read bids
readBids(bidFileName);
}
void determineWinners() {
// for each item {
for (int i = 0; i < nItems; ++i)
{
// resolveAuction(item);
resolveAuction(i);
}
}
void cleanUp()
{
cleanUpItems();
cleanUpBidders();
cleanUpBids();
}
int main (int argc, char** argv) {
// Determine winners of daily auction;
initializeAuctionData(argv[1], argv[2], argv[3]);
determineWinners();
cleanUp();
return 0;
}