520 likes | 652 Views
The STREAM process – programming in practice. What are we trying to do?. Tools Language Specifications Intellect. Programs that work…. HOW?. The programming process. What elements are involved in writing a (correct) program? In the ideal world:
E N D
What are we trying to do? Tools Language Specifications Intellect Programs that work… HOW?
The programming process • What elements are involved in writing a (correct) program? • In the ideal world: • Understand – read the specification until you are sure you understand it completely • Think – now think hard about how to solve the program • Code – now write the program! • A simple, linear process…but it does not work that way in real life
The ideal world Understand Think Code
The real world Understand Think Code Test
How to get started • We will not address the problem of understanding – and develop-ing – the specification here (Software Design) • Given a reasonable under-standing of the problem to solve, how do we get started at all? • The traditional approach is stepwise refinement
Stepwise refinement public class MyMainClass { public void solveProblem() { // Just add code here… } }
Stepwise refinement • From this point, simply break down the problem into smaller and smaller pieces • Code evolves from being abstract to becoming concrete • Not a bad strategy, but still too naive • Assumes perfect knowledge of problem • Still a linear process • A more realistic process is stepwise improvement
Stepwise improvement • The Stepwise improvement process adds two more activities to the Refinement process • Extension: we extend the code to cover more of the specification – for instance include an additional case • Restructuring: we change the structure of the code, without changing the functionality of the code
Activity diagram Refinement The Goal You are here! Extension Restructuring
Stepwise refinement Refinement The Goal Extension Now we understand the problem com-pletely, start coding! Restructuring
Stepwise improvement Refinement The Goal Extension Restructuring
Embrace change… • Is Stepwise Improvement completely chaotic!? • No, but it can appear confusing • There is no given order of the three types of activities, they can ”happen” at any time during program development • Notice, however, that they are separate activities • We can provide advice about how to perform each activity
Extension • In real life, a full specification is often very large, hard to under-stand, incomplete and ambiguous • Does not really make sense to try to make a complete program in one attempt • Instead, select a part of the functionality, and make it work
Extension • Once the selected part of the specification works, we can proceed to including more parts of the specification • Before that, we may possibly do some refinement and restructuring • The specification itself might change as well…
Refinement • In this activity, we try to actually implement some part of the specification • This activity only involves programming • Do not try to be too clever here! • Refinement is complicated enough in itself • The STREAM process is a simplified process for the refinement activity
The STREAM process • Stubs • Test • Representations • Evaluation • Attributes • Methods
Stubs • What are ”stubs” – methods which do not yet have code inside them • We assume that at this point, we are about to implement some well-known classes • Well-known: The expected behavior of the classes is known • We should be able to figure out the public interface to the classes
Stubs public class BankAccount { public void deposit() {} public void withdraw() {} public double getBalance() { return 0.0; } }
Stubs • We can create classes with stub methods for all classes under consideration • Our program will compile…but do nothing • This state of a program is often called a walking skeleton
Test • We assume we fully understand the classes under consideration • We should thus be able to write test cases for them, before proceeding further • Gives us a clear target – once the program passes our tests, we are done with the activity • Usual considerations about test still apply…
Representation • During the ”stub” part, we did not pay any attention to data representation • Data representation is not part of the public interface • Need to make a choice, however… • Think about alternatives for represen-tation – you should come up with at least two alternatives
Evaluation • Given some alternatives for representation, we must now evaluate them • Evaluate: How hard will it be to implement the methods in the public interface, given that we use some specific representation • Will of course be a subjective evaluation • Evaluate each representation alternative for each public method • REM: Representation Evaluation Matrix
Attributes • Now we have chosen a representation for our data, so implement the representation • That is: • Define instance fields • Set proper values in class constructors • This does not mean that we are done with representation – we may need more instance fields when implementing methods • Can still use REM
Attributes public class BankAccount { double balance; public BankAccount() { balance = 0.0; } public void deposit() {} public void withdraw() {} public double getBalance() { return 0.0; } }
Methods • All that remains now is to fill in the code in the public methods… • …but that is also the hardest part! • In a sense, we try to re-apply parts of the STREAM steps when implementing a method • We have a set of method implementation rules to assist us during implementation
Methods • Again, the guiding principle is to manage the complexity, by breaking problems into smaller, more manageable pieces • In practice – push your problems ahead of you! • Keep method implementations simple, by inventing new methods as you go along • Implement the new methods as stubs, and return to them later on • The mañana principle (or the Good Fairy)
The mañana principle public void convertFromInchesToCm() { double inches = getInchesFromUser(); double cm = calculateCmFromInches(inches); presentCmToUser(cm); } private double getInchesFromUser() { return 0.0; } private double calculateCmFromInches(double inches) { return 0.0; } private void presentCmToUser(double cm) {}
The mañana principle • When to use the mañana principle • Special case: Move implementation of a special case to a separate method • Nested loop: If you have a nested loop, move the inner loop to a separate method • Hard problem: If you need the answer to a problem that you cannot immediately solve, make it a separate method • Heavy functionality: If a sequence of statements or an expression becomes long or complicated, move some of it into a separate method
Methods • The mañana principle is not a rule, but a principle – apply when necessary • The ”threshold” for when to apply it is individual • Rule-of-thumb: Methods which cannot fit into a single screen are too long, and should be divided into more methods • Remember we still have the test cases to guide us – when the tests are passed, we are done
Keeping the compiler happy • This is a very practical advice • Compilers try to find all errors in the code, but often get it wrong… • Error messages from the compiler can be quite confusing • Only try to fix those errors which make sense • Fortunately, compilation is fast!
Keeping the compiler happy • Compile often! • Try to keep the program free of syntax errors at all times • Fix errors immediately • If the compiler only reports one or a few errors, they are much easier to fix • Error(s) must be in the code you have just entered…
Example: a Date class • We are given the task of implementing a Date class, specified as follows: • The Date class represents a date, given by year, month number and day number • When created, a Date object is initialised with a year, month and day (it can be assumed that the input data always represents a valid date) • It must be possible to advance the date in a Date object to the next valid date • It must be possible to retrieve the data as a String
Date class – Stubs public class Date { public Date(int year, int month, int day) { } public void advanceToNextDate() { } public String toString() { return null; } }
Date class – Test • We will not detail this now, but there are actually a lot of cases to test • Simple advance of date • Advance of date across month • Advance of date across year • Leap years • …etc. • But we can just write up the test cases, without worrying about implementation
Date class – Representation • What are possible – and reasonable – represen-tations of a date? • String – we need to get it back as a String • Multiple integers – one for day, one for month and one for year • One integer – days since Jan 1st, 0001 • We evaluate the alternatives using the REM (Representation Evaluation Matrix)
Date class – Attributes public class Date { private int year; private int month; private int day; public Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } public void advanceToNextDate() { } public String toString() { return year + ”-” + month + ”-” day; } }
Date class – methods • The only task left now is to implement the advanceToNextDate() method • First attempt: public void advanceToNextDate() { date = date + 1; }
Date class – methods • A little naive, but actually works in 97 % of all cases • We do need to handle special cases • Next attempt: public void advanceToNextDate() { date = date +1; handleDayOverflow(); }
Date class – methods • Now advanceToNextDate() is done, assuming that handleDayOverflow()works! • The mañana principle at work! • Now on to handleDayOverflow() private void handleDayOverflow() { if (day > 30) { day = 1; month = month + 1; } }
Date class – methods • This will work, except for two situations • Month does not have 30 days • Across a new year • Once again, we fix this by inventing some new methods and implement them later: • daysInMonth() • handleMonthOverflow()
Date class – methods private void handleDayOverflow() { if (day > daysInMonth()) { day = 1; month = month +1; handleMonthOverflow() } } private int daysInMonth() { return 30; } private void handleMonthOverflow() {}
Date class – methods • And we just keep going like this… • The final implementation will – of course – handle all cases correctly, including leap years • Manage the complexity – only try to solve one problem at a time! • Do not think too much about whether or not the code is optimally structured – this is adressed during the restructuring activity
Restructuring • During a refinement activity, focus is on achieving functionality • Should not be too concerned with code structure • During a restructuring activity, we restructure code but preserve functionality • Easy to forget/neglect this activity in real life…
Restructuring • How do I know what to restructure…? • Not an easy task, requires some experience • You have to develop an ability to ”smell bad code” • What does bad code smell like?
Smelly code… Hmm, bad choice of names…? private void hDO() { if (d > dIM()) { d = 1; m++; hMO() } } private int dIM() { return stdDiM; }
Restructuring • It could be as simple as that… • In the heat of the fight, we might have chosen names which are ”non-informative” • Similar ”clean-up” activities could be: • Non-consistent naming • Replace magic numbers with constants • Fixing alignments • Eliminate unused parameters • Remember, no change in functionality!
Smelly code… public void printWorkerInfo(Employee worker) { System.out.println(”Worker information”); System.out.println(”-------------------------”); System.out.println(worker.GetName()); System.out.println(worker.GetAddress()); } public void printManagerInfo(Employee manager) { System.out.println(”Manager information”); System.out.println(”-------------------------”); System.out.println(manager.GetName()); System.out.println(manager.GetAddress()); }