Journal Articles
Browse in : |
All
> Journals
> CVu
> 155
(10)
All > Journal Columns > Code Critique (70) Any of these categories - All of these categories |
Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.
Title: Student Code Critique Competition
Author: Administrator
Date: 03 October 2003 13:16:00 +01:00 or Fri, 03 October 2003 13:16:00 +01:00
Summary:
Body:
What would you do to help this student with his programming? Note that this time the student knows enough about C++ to write a suitable program, so the problem is in the mind to the extent that I think he is even confused by what is needed as output.
I am working on a program that will tell someone how old they are in years, days and months. I.e. You are 27 years, 200 months, and 1500 days old.
#include <iostream> using namespace std; void main() { int currentyear = 2003; int dob = 0; int result = 0; int day = 0; int month = 0; cout << "Enter your year of birth: "; cin >> dob; result = dob - 2003; cout << "You are " << result << " years old" << endl; cout << "Enther you month of birth: "; cin >> month; for(result=0; result <=12; result++) if(result <= 12) result = month+12; cout << "You are " << result << " months old"<<endl; cout << "Enter you day of birth: "; cin >> day; for(result=0; result <= 365; result++) if(result<=365) result = day+365; cout <<"You are " << result << " days old"<<endl; }
Of course remember to comment on the program defects but your main focus should be how to explain the need for clear thought when producing a solution.
This time I had just two entries that took very different approaches. In my opinion neither managed to display clarity of thought in the context of code. Both provide ideas about clear thinking before you start coding. I wonder what you think?
I also wonder why you did not enter. Yes I do mean you, the person reading this now. Perhaps you thought about it but never actually got round to doing it. Perhaps you did it but thought it not worth sending in the result. Well that certainly makes my life easier and it also makes it easier for James to choose a winner. However that would be to miss the point: the real benefit from this column is not in reading it but in participating.
If you cannot sit down and bash out a reasonable submission to most of these code critique exercises in a couple of hours then you have a great deal still to learn. What is that I hear you say? OK so you are an expert, but how do you intend to give constructive help to the struggling newcomer to your team?
Please think about it and aim to try at least one critique every year. If you are competent you should not fear the criticism of your peers, and if you do fear such criticism you already doubt your own skills and should welcome the chance to learn a little more.
Francis asked for a critique of a short programme derived from the following requirement, "I am working on a programme that will tell someone how old they are in years, days and months. I.e You are 27 years, 200 months, and 1500 days old."
There are two problems to be solved for this critique. The first is to try and explain the need for clear thought when trying to produce a solution. The second to identify defects in the code as supplied with an unwritten requirement for a sample of code that works,
So how on earth do we explain or demonstrate how to think clearly? Perhaps it is worth taking a step further back from the impossible and showing how a clear problem definition can make it easier to think clearly about the problem in hand. It doesn't remove the need for clear thought, but it provides a sieve that you can use to filter out the rubbish that might be cluttering your thoughts. The better the problem definition the finer the sieve and, one hopes, the clearer the resulting thoughts.
Let me try and demonstrate the process.
Taking the original summary of the programme's purpose it looks like the programme should report the user's age in three different formats rather than as a single, "You are 27 years, 6 months and 21 days old" result. Why? Well, normally you would not report month values greater than 11 or days greater than 364. If that is the case then the sample data doesn't make this clear as the figures yield 3 different ages, 27, not quite 17 and a little over 4 years. However looking at the code, well I'm really not sure what it's trying to do, but I think it may in fact be trying to report the age as a single, "You are x years, y months and z days old." statement.
So how are we to solve this problem? Which problem? Good point, let's step back a bit and see if we can identify what problems we have. In no particular order:
-
How are we to calculate the user's age?
-
What calculations are we to perform? What are the "business" rules?
-
How is the result to be presented to the user?
-
Who is going to be using the proposed programme?
-
Why is a programme required? Do we really need a computer to achieve a result?
Well the first problem on the list is probably the least important of them all, it will come down to (ha ha) a SMOP (simple matter of programming) and providing the rules used to calculate ages are followed correctly any one solution is as likely to be as good as any other.
The last question is always worth asking. Is this a one off task where the result can be obtained using a pencil and paper with, perhaps, the backing of an electronic calculator?
Correct answers to the remaining questions are vital to the successful outcome of our project. So how are we going to answer these questions? Simple. We're going to have to ask someone and more to the point someone who knows about the problem area.
The really difficult part of our trade is that there's only one way of finding answers to the questions above. That is talking to other people and extracting information and decisions from them. This is a skill every bit as important as being able to think clearly or knowing how to write the leanest, meanest most efficient code ever in the most fashionable language of the moment.
So how might this go in the commercial world? Well let's imagine....
"Good morning Sir. I'm from the I.T. dept. Have you a few minutes, perhaps half an hour to talk about your request for a programme to report someone's age?
What!? Can't you read? I sent a memorandum to your head chap detailing my requirements last week!
Ah, yes Sir. I have a copy here. I have read it, but there are still a few grey areas that we need to clear up with you and...
Oh, very well then. If you must, you must, but I'm a busy man so it had better not take too long.
Well, shall we start with how the programme is to find out how old the user is...
Isn't it obvious? He or she will enter their date of birth!
Ah, yes. We did rather expect that, but how will they enter their date of birth?
Do you want them to be able to enter a date as they would on a paper form or is the user to enter each part separately?
Don't play silly beggars with me young woman, as you would on a form of course!
Ha hm. Just so. Ah, is this programme to be used in our overseas offices?
Now just what the blazes has that got to do with anything?
Well if we're going to ship it to all our overseas branches then the staff in each branch will probably want to use the calendar that they're used to..
What are you blethering on about?
Well the Islamic, Hebrew and Chinese calendars are in common use in some of our offices and I'm not certain but I think that one or two our branches in the Balkans are still using the Julian calendar..."
... and so it went on. Our heroine ploughed through a list of questions that she had, adding others that arose out of the discussion. Then, after a good deal of explanation that a programme written to a limited specification would be quicker to produce and above all would be far, far, far cheaper to produce than one that dealt gracefully with locale issues, she presented the following to Colonel Blimp her customer...
"A programme shall be written that will report the user's age as the number of complete years since the user's birth, the number of complete months since the user's birth and the number of complete days since the user's birth.
When the programme is run the user shall be prompted to enter first the year, then the month and finally the day of their birth.
The programme shall not attempt to calculate the age of the user until all of the date information is entered.
- For example:
Please enter a year of birth : 1904 Please enter a month of birth : 6 Please enter a day of birth : 13 Age at end of 2003/09/02 was 99 years, 2 months, 21 days.The programme shall apply the following validation rules to the entered data.
- Years
From (current date - 130) to current date. Inclusive. For example in 2003 the range would be 1873 to 2003 inclusive.
- Months
1 to 12. Inclusive
- Days
1 to maximum number of days in month. Inclusive.
If a user enters a value outside a valid range he or she will be asked to enter a value that lies within the valid range.
- For example:
Please enter your year of birth : 1837 Please enter a value between 1873 and 2003. Please enter your year of birth :- In addition:
The Gregorian calendar shall be used.
Users shall not be allowed to enter dates in advance of the current date."
All that work, for such a simple programme and we haven't even begun to suggest any solutions never mind cutting any code.
By the way there's a bear trap in the foregoing. The customer may not be, is rarely, the user and may just have led you right up the garden path. So in the ideal case we'd go and talk to some typical users as well before we went any further. That's the ideal though...
Well what we've got is not complete, first attempts almost never are, but it is a clearer statement of the problem that we can discuss with colleagues and it can be used as a basis for sensible design decisions and, just as important, we have the beginnings of something that we can test against.
So why would we want to discuss our problem definition further? Well, let's start with date entry. We want to prevent the entry of dates in advance of the current date. This might be simpler if the user entered the complete date at once, but then we have the complicating factor of format validation. Then there's the matter of valid year ranges. We've said "130 years before current date" and given an example. But do we really mean 'current date' or should we have said 'current year'? And what about that 130 limit itself? What happens if we want to know the age of an elderly Galapagos tortoise? Oh, and how do we define a 'complete' month and what about someone whose birthday falls on the 29th of Feb?
So, where are we in our attempt to show at least one way of reducing vulnerability to muddled thought?
-
Define the problem. Unbounded problems will run away with you. How do you define the problem? Well...
-
...find out as much as you can about the problem area from those who know the problem area well.
-
Try to consider the problem from the point of view of the person who posed it in the first place. Go through the problem definition slowly and ask yourself, "What if this circumstance were to change?" and "Why on earth was that value chosen?"
-
Write your problem restatement/expansion down in simple, short words that anyone can understand. Get someone else - a colleague and especially the customer - to read it. If you can explain the problem simply to someone else and they understand what you tell them then there's a very good chance that you understand the problem well enough to actually solve it.
-
One more time, simple, short words. Avoid jargon if you can.
-
Never regard your first attempt at problem (re)definition as the last word in all but the most trivial cases. Of course you then have a supplementary difficulty, how trivial is trivial?
At this point you can then sit back under your (metaphorical) tree and try and feed candidate solutions through your sieve.
I haven't got much to say about this as, as I said earlier, how on earth do you teach clear thinking? So perhaps a list of points that work for me will at least give you something to start with…
-
Don't try to solve the whole problem at once. If at all possible break the problem down into manageable pieces.
-
Pick a section of the problem and work on it. Write things down. Nothing is likely to reduce clarity of thought faster than trying to maintain more information in your head than your mind can comfortably handle. Levels vary from person to person.
-
When you are working on other sections of the problem be prepared to revise earlier work in the light of new discoveries. Revision might mean scrapping and starting again.
-
Take breaks from the problem, clear your mind. Make a coffee or go for a short walk, do the washing up and if you are really stumped go and talk to your spouse/partner/cat. Anything just so long as it is completely unrelated to the work at hand.
-
Allow yourself enough time to deal with the problem. Panic and its firstborn muddled thinking are very often the products of a ridiculous deadline. If you do a good deal of commercial coding you will, more's the pity, find this out.
So what about the original code? Well as the original statement of requirements is a little vague there's not a lot that I can say and I have as many questions as criticisms.
-
Surely it should be '2003 - dob'? If that's the case then it won't work for any year but 2003.
-
Was 'currentyear' meant to be retrieved from current date and the literal 2003 substituted for the sake of testing?
-
There's no validation of input data.
-
There is no explanation of the intention behind the arithmetic used to calculate month and day values and I cannot deduce the intent by reading the code.
-
Why the choice of 'dob' for year of birth? Was the original intention to allow the user to enter a customary date such as 11/09/37?
-
Was there an undocumented requirement to avoid using the standard library date and time functions?
Well having written all that the very least I can do is offer a possible solution to what might have been the original problem. It is flawed, but then you, dear reader, are the first reviewer of the work, the work was done without collaboration and (through no other fault but my own) I left it until too close to the deadline to start so poor design choices have been left as they were. Now, remind me, what was I saying earlier? :)
// Warning! Only (lightly) tested with g++ on // FreeBSD 4.6.2 For student code critique 23. #include <iostream> #include <ctime> // in g++ this is an alias // for time.h - it may not always be so. using namespace std; // Assumptions - // Gregorian calendar is going to be used. // We don't expect to find anyone older than // about 125. // All sorts of assumptions about current // date, time and we had // to have a look at time.h to check what we'd get back. const int MAX_LIFETIME_YEARS = 130; const int SYS_BASE_YEAR = 1900; const int MONTHS_IN_YEAR = 12; enum eMonth { Unknown, January = 1, February, March, April, May, June, July, August, September, October, November, December }; bool IsLeapYear(int Year) { // Leap years in the Gregorian calendar occur // in years that are exactly divisible by 400. // Non-centenary years that are exactly // divisible by 4. if(Year % 100 == 0 && Year % 400 == 0) return true; if(Year % 100 != 0 && Year % 4 == 0) return true; return false; } int DaysInMonth(int Year, eMonth Month) { /* Thirty days hath September, April, June & November, All the rest have thirty-one. Excepting February which hath 28 except... */ int days = 31; switch (Month) { case February: if(IsLeapYear(Year)) days = 29; else days = 28; break; case September: case June: case April: case November: days = 30; break; } return days; } int main() { int Day = 0; int Month = 0; int Year = 0; // We mustn't allow anyone to enter a date // later than the current date. time_t NowInSeconds = 0 ; tm* pCurrentDate; int CurrentYear = 0; int CurrentMonth = 0; int CurrentDay = 0; // Find out what the current date is. time(&NowInSeconds); pCurrentDate = localtime( const_cast<time_t* > (&NowInSeconds)); CurrentYear = SYS_BASE_YEAR + pCurrentDate->tm_year; int ApplicationBaseYear = CurrentYear - MAX_LIFETIME_YEARS; CurrentMonth = 1 + pCurrentDate->tm_mon; CurrentDay = pCurrentDate->tm_mday; // Let's keep a track of where we are. cout << "Currently : " << CurrentYear << "/" << ((CurrentMonth < 10) ? "0" : "") << CurrentMonth << "/" << ((CurrentDay < 10) ? "0" : "") << CurrentDay << '\n'; while(Year < ApplicationBaseYear || Year > CurrentYear) { cout << "Please enter a year of birth: "; cin >> Year; if(Year < ApplicationBaseYear || Year > CurrentYear) cout << "\tPlease enter a year from " << ApplicationBaseYear << " to " << CurrentYear << '\n'; } int MaxMonth = MONTHS_IN_YEAR; if(Year == CurrentYear) MaxMonth = CurrentMonth; while(Month < 1 || Month > MaxMonth) { cout << "Please enter the month of birth: "; cin >> Month; if(Month < 1 || Month > MaxMonth) { cout << "\tA month from 1 to " << MaxMonth << " please.\n"; Month = 0; } } int MaxDay = DaysInMonth(Year, static_cast<eMonth>(Month)); if(Year == CurrentYear && Month == CurrentMonth) MaxDay = CurrentDay; while(Day < 1 || Day > MaxDay) { cout << "Please enter a day of birth: "; cin >> Day; if(Day < 1 || Day > MaxDay) { cout << "\tA day between 1 and " << MaxDay << " please.\n"; Day = 0; } } int WholeYears = CurrentYear - Year - (CurrentYear>Year? 1 : 0); int WholeMonths = 0; int WholeDays = 0; if(CurrentMonth == Month) { if(CurrentDay >= Day) ++WholeYears; else WholeMonths = MONTHS_IN_YEAR-1; } else if(CurrentMonth > Month) { if(CurrentYear > Year) ++WholeYears; WholeMonths = CurrentMonth - Month- 1; } else { WholeMonths = MONTHS_IN_YEAR - Month + CurrentMonth; if(CurrentDay < Day) -WholeMonths; } if(CurrentDay >=Day) { WholeDays = CurrentDay -Day; } else { int PreviousMonth = (CurrentMonth + MONTHS_IN_YEAR -1) % MONTHS_IN_YEAR; WholeDays = DaysInMonth(CurrentYear, static_cast<eMonth>(PreviousMonth)) - Day; if(WholeDays < 1) WholeDays = 0; WholeDays = WholeDays + CurrentDay -1; } cout << "Age at end of " << CurrentYear << "/" << ((CurrentMonth < 10) ? "0" : "") << CurrentMonth << "/" << ((CurrentDay < 10) ? "0" : "") << CurrentDay << " was " << WholeYears << " year" << ((WholeYears != 1) ? "s " :" ") << WholeMonths << " month" << ((WholeMonths != 1) ? "s" :"") << " and " << WholeDays << " day" << ((WholeDays != 1) ? "s." :".\n"); return 0; }
Colin's code caused me some problems because there is a relatively large amount of it and in the original he had added a considerable amount of comment. Space is always at a premium in C Vu so I removed most of the comments. The result may be less readable but it is almost a page shorter, which, in this context, matters.
There are several points that I want to comment on before moving on to the next entry.
To my mind main is clearly far too large and includes far too many nested structures. While Colin has carefully validated the input in regards to the numerical values he has not checked for successful input. In real life, in my experience, the latter problem is far more common.
A large proportion of his code (and most of the nested conditionals) is the result of the way he has approached the problem. How could he have done it differently? Consider:
int years = current_year birth_year; int months = current_month - birth_month; int days = current_day - birth_day; if(days < 0) { --months; days += days_in[leap][current_month-1] } if(months < 0) { --years; months += months_in_year; } if(years < 0) { cout << "You haven't been born yet.\n"; else cout << "Your age is:" // etc
Oh, that days is in because I would always use a look up table for this kind of irregular data. In this case I would store the number of days in December as both the first and the last entry. Check that out, it ensures that January works correctly even when the current date is earlier than a January birthday. The other advantage of a look-up table is that it makes the validation code for birth_day much simpler. leap will have been evaluated earlier.
One thing always worth considering is distinguishing between unlikely data and impossible data. The claim to have been born in 1503 is (extremely) unlikely but the claim to have been born in the 15th month or the 33rd day of the seventh month is impossible (for a Gregorian Calendar). I tend to favour accepting unlikely data but perhaps verifying it.
(Editorial note: I would not use a lookup table here as - to my mind - it adds complexity rather than clarity. But that just shows that simplicity and elegance is hard to pin down, and even experienced programmers can reasonably disagree sometimes. James)
from Walter Milner <<w.w.milner@bham.ac.uk>>
We will firstly check through the program as you have written it, then take a broader look.
Firstly
void main()
might often be written as
int main(void)
together with a
return 0;
at the end of function main.
[Not strong enough. It should be written with an int return type unless you are happy to write non-portable code relying on a vendor extension. Francis]
Then you declare
int currentyear = 2003;
This works this year, but you would have to rewrite the program next year. Ideally one would get the program to use the system to tell you the current date. However you seem to have forgotten what you've done when you write
result = dob - 2003;
using the 'magic number' 2003 rather than
result = dob - currentyear;
However suppose dob is 1983, then result = 1983 - 2003 = -20. You should have said
result = currentyear - dob;
This raises the question of naming variables. I suppose dob is 'date of birth', but it is used in the program in the sense of 'year of birth'. Does the difference matter? Yes. There are a variety of naming conventions, but it is generally accepted that variable names should reflect as closely as possible the 'meaning' of the variable, and this often prompts you to ask yourself 'what exactly is this variable about?' which is often a very useful question. You can probably now see the problem with the name 'result'.
We then have
for(result=0; result <=12; result++) if(result <=12) result = month + 12; cout << "You are " << result << "months old" << endl;
Let's see what this will do. The value of month will be between 1 and 12 (if the user entered a valid value). The first time into the loop, result will be 0, so
result = month + 12;
will give result a value between 13 and 24. Then result++ will add 1 to this, and since result will be 14 or more, the loop will terminate. If I was born in January it will tell me I'm 14 months old.
So what did you think it would do? You have a ' + 12' inside a loop - so are you adding 12 for each year of the person's life? If so you would have needed to say
result += 12;
then added month in once after the loop for the incomplete year. But
-
you loop through this 13 times, not for each year of life, and
-
you use result both as the loop counter and to hold the required answer.
Using more precise variable names, such as yearOfLife or monthOfLife, might have made the problem slightly more apparent to you.
You repeat the same code pattern in
for(result=0; result<=365; result++) if(result<=365) result = day + 365; cout << "You are " << result << " days old" << endl;
Before you repeat a code pattern - make sure the original works.
We start from the question -
'What is this program supposed to do?"
You say it should 'tell the user how old they are in years, days and months i.e. 27 years 200 months and 1500 days old'
I think you mean "e.g." not "i.e." Now this might mean how old in complete years, complete months and days. Alternatively it might mean how old in years (ignoring part years) or in months (ignoring part months) or in days (ignoring... you've got it). Your example seems to suggest the latter, but not clearly, since someone who was exactly 27 years old would have be 324 months old, not 200. Before writing the program, you must be clear as to what it is supposed to do. Let's take the second interpretation here.
Einstein is supposed to have said
Keep it simple
which has been made into the acronymn KISS, keep it simple, stupid. However Einstein added the more useful caution
But not too simple.
In the context of calendars, dates and times, 'not too simple' is really quite complicated. For a start, what do you mean by month - calendar or lunar? Well calendar is a lot easier here, so we'll do that. As another example, calculating the number of complete years involves not just the year of birth and the current year. For example, the current date is 21 August 2003. If my date of birth was say 1 November 2002, my age in complete years is not 2003 - 2002 = 1. It is zero - I have not yet lived 12 months. Similar ideas apply for months and days. In this way we have a more precise specification for the program:
- input:
-
date of birth (day month and year) and current date (day month and year)
- output:
-
age in complete years, age in complete calendar months and age in complete days.
How do we do it? Well, you need to re-submit this assignment so I will not do it for you, just outline two approaches. The first is to use calendar and date library functions which will essentially do it for you. However that is not what you are expected to do here. The second approach is to do it yourself, as follows:
Firstly calculate the complete years. You would need to compare the current month and the month of birth, and if they are equal, the birth day compared to the current day. It might help to draw some diagrams, or work out some examples on paper.
Then calculate the complete months. This is the complete years times 12 plus the difference in the months.
Then the days. The simple approach is the complete years multiplied by 365, plus the days in the months - which depends on which months they are.
The correct approach takes leap years and leap centuries into account - and this is what makes libraries so nice.
The editor's choice is: Colin Greenock
Please email <francis@robinton.demon.co.uk> to arrange for your prize.
(Submissions to <francis@robinton.demon.co.uk> by Nov. 14th)
Curious what novices set out to do but that should not stop us from trying to help them achieve their objectives. I suspect that the writer of this is trying to grab the console output and paste it into a file. Now we know what he should be doing, but while helping him try to raise the code quality.
I want to copy my C++ program output to an ASCII text file
I store my data in arrays and then use coutto output the data in DOS... as I generate data in the order of 1000's ...I am having a problem with physically copy pasting the data into notepad (an MS Windows pure text editor).
Here is my code...please suggest what changes are necessary. My coding is not really that efficient. Please bear with me
# include<iostream.h> # include<conio.h> # include<math.h> # include<iomanip.h> void main(){ void clrscr(); int i,r,j,m,x; static Sum[5000][5000]; Sum[1][1]=25000; Sum[2][1]=35000; // input the data into arrays for(j=2; j<=11; j++) for(i=1; i<=pow(2,j); i++) { if(i==1) { Sum[i][j] = Sum[i][j-1] + 25000; Sum[i+1][j] = Sum[i][j-1] + 35000; } if(i>1) { Sum[i][j] = Sum[r][j-1] + 25000; r=r+1; Sum[i+1][j] = Sum[(i+1)/2][j-1] + 35000; } i=i+1; r=2; } // output the data for(m=2; m<=11; m++) { for(x=1;x <= pow(2,m); x++) { for(i=1; i<=2; i++) { cout << x << m << setw(10) << Sum[x][m] << '\n'; } } getch(); } }
My output reads:
11 25000 11 25000 12 35000 12 35000
so on..so forth
Notes:
More fields may be available via dynamicdata ..