Xref: utzoo comp.object:3266 comp.lang.misc:7547 Path: utzoo!utgpu!news-server.csri.toronto.edu!rpi!batcomputer!cornell!uw-beaver!ssc-vax!dmg From: dmg@ssc-vax.uucp (David M Geary) Newsgroups: comp.object,comp.lang.misc Subject: Re: Readability of Ada Message-ID: <3878@ssc-bee.ssc-vax.UUCP> Date: 19 Apr 91 21:06:08 GMT Sender: news@ssc-vax.UUCP Organization: Boeing Aerospace & Electronics Lines: 553 Originator: dmg@ssc-vax ] Jim Showalter ] A short while ago, I posted an example of what I view as fairly ] typical C code and asked people what it did. Given that the code ^^^^^^^^^^^^^^(1) ] was, as usual, written with two-character "identifiers" and ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^(2) ] maximum utilization of C's cryptic syntax, it took people a while ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^(3) ] to figure out that the intent of the code was to compute the Julian ] date. Even then, all but a handful of people missed the two bugs ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ] planted in the code. ^^^^^^^^^^^^^^^^^^^ (4) 1) The posted code was *not* typical C. 2) What? *as usual*, written with two-character "identifiers"? After nearly 10 years of C programming, and looking at millions of lines of others C code, I have *never* seen *anyone* that used *as usual* 2 char identifiers for variable names. Maybe Jim is confusing Unix's 2 char. names for commands such as: ls cc rm df ... etc. But that is *Unix*, not *C*. 3) Oh yea, that's what I always try to do when I write C code - write with maximum utilization of C's cryptic syntax. Get real. 4) So people could not figure out Jim's incredibly bad C code. So what? I can write code in Ada that noone can understand, either. ] Others challenged me to provide the same functionality in Ada, for ] comparative purposes. Very well--what follows is a skeleton of a ] Julian_Calendar package, written in Ada. I have not fleshed out the ] package with all the other functionality that would logically belong ] there (e.g. computation of what day of week it was on an arbitrary ] date, etc). [I have also stuck to the same basic algorithm as in ] the C example, to make the comparison straightforward--if I were ] really implementing a Julian_Calendar package for commercial consumption, ] I'd do so using a universal calendar algorithm, for speed.] ] The package does compile, execute, and give the correct results. This is the most ridiculous thing I've ever seen. What's the point? I can write garbage code in Ada, that noone could ever figure out, and write readable C code. So can anyone. So what? ] Writing as well as possible in C, one can still not write ] programs that are as readable and understandable as programs ] written in Ada by an equally competent programmer with the same ] objectives in mind. Oh, come on. I take back the claim above about the most ridiculous thing I've ever seen. *This* is the most ridiculous thing I've ever seen... This is total, absolute, unabashed crap. Then Amanda Walker writes: ]] Your Ada example seems to be something of a red herring; you could write ]] an Ada function that's just as hacky and scrunched up as your C example. ]] Holding these two examples up as equivalent, and thus proving your ]] point, is a straw man argument... Exactly. Although I would change the "seems to be something of" to "is a" in Amanda's reply ;-) Well, I was not about to be goaded into writing the julian code in C, but the last statement I quoted above from Jim has pushed me over the edge. Here is the code Jim has asked for in C: .................. BEGIN C CODE FOR JULIAN DATE COMPUTATION .................. #include /*| -------------------------------------------------------------------------- | | I normally keep the assertBool() macro, PRIVATE, and PUBLIC #defines, | and BOOLEAN type definition in a header file which is included with | all my .c files... */ /* assertBool() - if ex is not true, then the message "Assertion Failed: " | is printed to stderr, citing the file and line number | in which the assertion failed... */ #define assertBool(ex) \ { \ if(!(ex)) \ { \ fprintf(stderr, "Assertion Failed: %s, %d\n", __FILE__, __LINE__); \ return 0; \ } \ } /* | Functions below which are declared PRIVATE are only visible in this file, | and thus not available to functions outside of this file... | | Functions which are not PRIVATE, are explicitly declared as PUBLIC, so | that it is clear that the function was originally meant to be PUBLIC. | PUBLIC functions are available by functions not residing in this file... | | TRUE and FALSE are 1 and 0, respectively... */ #define PRIVATE static #define PUBLIC #define TRUE 1 #define FALSE 0 /* BOOLEAN portrays more meaning than simply int ... */ typedef int BOOLEAN; /* -------------------------------------------------------------------------- */ int daysPerMonthNormalYear[] = { /* January */ 31, /* February */ 28, /* March */ 31, /* April */ 30, /* May */ 31, /* June */ 30, /* July */ 31, /* August */ 31, /* September */ 30, /* October */ 31, /* November */ 30, /* December */ 31 }; int daysPerMonthLeapYear[] = { /* January */ 31, /* February */ 29, /* March */ 31, /* April */ 30, /* May */ 31, /* June */ 30, /* July */ 31, /* August */ 31, /* September */ 30, /* October */ 31, /* November */ 30, /* December */ 31 }; /* | Here we define some macros to validate month, year, and day passed to | functions: */ #define validateMonth(mon) assertBool(month > 0 && month <= 12) #define validateYear(year) assertBool(year > 0) #define validateDay(year,month,day) assertBool(checkForValidDay(year,month,day)) /*-------------------------- BOOLEAN isLeapYear() --------------------------*/ /* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | AUTHOR: David Geary | DATE: 4/91 | | SCOPE: PRIVATE | | PURPOSE: To determine if a given year is a leap year. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | INPUTS: int year: year to determine (leap year) status of. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ASSERTIONS: BOOLEAN: year must be valid. | | FATAL: None. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | RETURNS: TRUE if year is valid and a leap year, FALSE otherwise. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | FUNCTION CALLS: SYSTEM: None. | | OTHER: None. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ PRIVATE BOOLEAN isLeapYear(year) int year; { validateYear(year); { return year % 4 == 0 && year % 400 != 0; } } /*------------------ int* getAppropriateDaysInMonthArray() ------------------*/ /* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | AUTHOR: David Geary | DATE: 4/91 | | SCOPE: PRIVATE | | PURPOSE: To obtain a pointer to the appropriate array of days per month, | depending upon whether the year is a leap year or not. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | INPUTS: int year: year to get appropriate array for. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ASSERTIONS: BOOLEAN: year must be valid. | | FATAL: None. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | RETURNS: Pointer to appropriate days per month if year is valid, NULL | if year is not valid. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | FUNCTION CALLS: SYSTEM: None. | | OTHER: None. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ PRIVATE int* getAppropriateDaysInMonthArray(year) int year; { validateYear(year); { if(isLeapYear(year)) return daysPerMonthLeapYear; else return daysPerMonthNormalYear; } } /*------------------------ BOOLEAN checkForValidDay() ------------------------*/ /* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | AUTHOR: David Geary | DATE: 4/91 | | SCOPE: PRIVATE | | PURPOSE: To validate that the value for a given day is correct, also | given the year and month. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | INPUTS: int year: year (what more can I say?) | int month: month (ditto) | int day: day (ditto) | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ASSERTIONS: BOOLEAN: year must be valid. | month must be valid. | | Note that an assertion is triggered if year or | month is not valid, but no assertion is triggered | if day is invalid. | | FATAL: None. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | RETURNS: TRUE if year, month and day are valid, FALSE otherwise. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | FUNCTION CALLS: SYSTEM: None. | | OTHER: getAppropriateDaysInMonthArray() | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ PRIVATE BOOLEAN checkForValidDay(year, month, day) int year; int month; int day; { validateYear(year); validateMonth(month); { BOOLEAN isDayValid = TRUE; int* daysInMonthArray = getAppropriateDaysInMonthArray(year); if(day < 1 || day > daysInMonthArray[month-1]) isDayValid = FALSE; return isDayValid; } } /*------------------------- int computeJulianDay() -------------------------*/ /* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | AUTHOR: David Geary | DATE: 4/91 | | SCOPE: PUBLIC | | PURPOSE: To compute the julian day, given a year, month, and day. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | INPUTS: int year: year. | int month: month. | int day: day. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ASSERTIONS: BOOLEAN: year must be valid. | month must be valid. | day must be valid. | | FATAL: None. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | RETURNS: Julian day, if year, month, and day are valid, 0 otherwise. | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | FUNCTION CALLS: SYSTEM: None. | | OTHER: getAppropriateDaysInMonthArray() | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ PUBLIC int computeJulianDay(year, month, day) int year; int month; int day; { validateYear(year); validateMonth(month); validateDay(year, month, day); { int monthCount = 0; int julianDay = day; int* daysInMonthArray = getAppropriateDaysInMonthArray(year); while(monthCount < month-1) julianDay = julianDay + daysInMonthArray[monthCount++]; return julianDay; } } /* | Normally, one would write a test program that resides in a file other | than this one ... */ main() { int julDay = computeJulianDay(1991, 4, 19); if(julDay == 0) printf("OOPS, either year, month, or day was not valid!\n"); else printf("Julian Day for April 19, 1991: %d\n", julDay); } .................. END C CODE FOR JULIAN DATE COMPUTATION. ................... I would say that the C code above is just as readable as Jim's Ada code. For me, it is actually much more readable, as I do not use Ada (but am familiar with it). For instance, in Jim's code we find: ] for Current_Month in January .. Month'Pred (This_Month) loop I do not understand *exactly* what this means, although I can glean that we are looping through months. The "Month'Pred (This_Month)" does not make sense to me. To me, this is much more readable: while(monthCount < month-1) Also, Jim does not clearly separate functions: package body Julian_Calendar is ] function Is_Leap_Year (This_Year : in Year) return Boolean is ] ] -- A leap year is any year evenly divisible by 4 that is ] -- not also evenly divisible by 400. ] ] begin ] return (This_Year mod 4 = 0) and not (This_Year mod 400 = 0); ] end Is_Leap_Year; ] ] function Julian_Day_For (This_Year : in Days_Per_Month; ] This_Month : in Month; ] This_Day : in Day) return Julian_Day is ] ] The_Day : Julian_Day := This_Day; ] ] begin ] if This_Year (This_Month) < This_Day then ] raise No_Such_Day; ] elsif This_Month = January then ] return The_Day; ] else ] ] -- Add up number of days in each month up to but not including ] -- the current month (current month is already taken care of ] -- by initial value of The_Day): ] ] for Current_Month in January .. Month'Pred (This_Month) loop ] The_Day := The_Day + This_Year (Current_Month); ] end loop; ] return The_Day; ] end if; ] end Julian_Day_For; ] ] function Julian_Day_For ] (This_Year : in Year; This_Month : in Month; This_Day : in Day) ] return Julian_Day is ] begin ] if Is_Leap_Year (This_Year) then ] return Julian_Day_For (Leap_Year, This_Month, This_Day); ] else ] return Julian_Day_For (Normal_Year, This_Month, This_Day); ] end if; ] end Julian_Day_For; ] ] end Julian_Calendar; This looks messy to me. There is no clear distinction (other than the keywords function, begin and end) as to where one function ends, and another begins. Jim, might I (a lowly C programmer) be so bold as to suggest an improvement for the readability of your Ada code? How about if you put a nice comment block before each function: (If I were an Ada programmer I would probably embelish the following comment blocks) -- --------------------------------------------------------------------------- -- -- FUNCTION: Is_Leap_Year() -- -- PURPOSE: To Determine if a given year is a leap year. -- -- RETURNS: true if year is leap year, false if not -- --------------------------------------------------------------------------- ] function Is_Leap_Year (This_Year : in Year) return Boolean is ] ] -- A leap year is any year evenly divisible by 4 that is ] -- not also evenly divisible by 400. ] ] begin ] return (This_Year mod 4 = 0) and not (This_Year mod 400 = 0); ] end Is_Leap_Year; ] -- --------------------------------------------------------------------------- -- -- FUNCTION: Julian_Day_For() -- -- PURPOSE: To compute the julian day for a given year, month and day. -- Notice that this function will raise an exception, if the -- This_Day value passed is invalid. -- -- RETURNS: Julian date for given year, month and day. -- --------------------------------------------------------------------------- ] function Julian_Day_For (This_Year : in Days_Per_Month; ] This_Month : in Month; ] This_Day : in Day) return Julian_Day is ] ] The_Day : Julian_Day := This_Day; ] ] begin ] if This_Year (This_Month) < This_Day then ] raise No_Such_Day; ] elsif This_Month = January then ] return The_Day; ] else ] ] -- Add up number of days in each month up to but not including ] -- the current month (current month is already taken care of ] -- by initial value of The_Day): ] ] for Current_Month in January .. Month'Pred (This_Month) loop ] The_Day := The_Day + This_Year (Current_Month); ] end loop; ] return The_Day; ] end if; ] end Julian_Day_For; ] etc. In addition, using a few more blank lines might make Jim's code a little more (is it possible?) readable. Also, a comment indicating what if an end if; statement terminates might help (just in case Jim writes if elsif, endif blocks (or whatever you call them in Ada) that are longer. Therefore the following: ] for Current_Month in January .. Month'Pred (This_Month) loop ] The_Day := The_Day + This_Year (Current_Month); ] end loop; ] return The_Day; ] end if; ] end Julian_Day_For; becomes: for Current_Month in January .. Month'Pred (This_Month) loop The_Day := The_Day + This_Year (Current_Month); end loop; return The_Day; end if; -- end of if This_Year (This_Month) end Julian_Day_For; Lastly, note that I am not an anti-Ada bigot. (Clearly, Jim is an anit-C bigot). Note the tone from one of Jim's earlier postings: ] Someone other than Jim. (Jim has an annoying habit of not crediting those he quotes in articles). ]] Jim. ] And I think C is really more suited for Gods (and daemons) to program in ] than humans. ]] Not gods--idiot savants. Note: the (and daemons) was cute ;-) Actually, I think Ada has quite a few nice features that C lacks. Personally though Ada is much too wordy for me. It's harder for me to see begin and end, for instance, than it is { and }. However, that's me, and, no doubt is due to the many years that I have spent writing C and C++ code. On the *average* Joe Blow's Ada code is *probably* more readable than Jim Blow's C code. However, to make a claim that no C program can *ever* be as readable as the same thing implemented in Ada is assinine. Lastly (oops ;-), note that the code above is very representative of the code I write everyday in C. In no way did I do *anything* in the above code that I would not normally do when writing C in everyday life. As a matter of fact, if I were to do a julian date computation program for real, I would have created classes (packaged structures and functions) such as year, month, and day, each of which would have functions that would do such things as validate(), isLeapYear() (for year class only), etc. I would then hide the data structures from everyone outside of the .c files that are associated with each class (yes, this can be done in C too), in addition to hiding certain functions which would not be part of the interface to each class. This would give me nice reusable *packages* which would, no doubt, mature over time.