dmg@ssc-vax.uucp (David M Geary) (04/20/91)
] 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 <stdio.h> /*| -------------------------------------------------------------------------- | | 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.
bs@alice.att.com (Bjarne Stroustrup) (04/21/91)
I think that it is interesting to note that the example used in this debate about the readability of Ada/C/C++ is in the domain where the solutions are essentially equivalent. Hence the diversion (?) of the debate into namin and commenting conventions. I do not think that there are any significant differences between the languages at this level so that the debate necessarily gets diverted into matters of taste, management, and sociology. Those discussions have their place, but I think that there is a significant issue in the readability of code where a language supports or fail to support a programming paradigm. So, how about comparing the languages on the example originally used to introduce the technique we now commonly refer to as object-oriented programming in the Simula textbook SIMULA BEGIN? The problem is to write a little program that define a set of shapes and manipulates them (I'll rotate a list of shapes). Then we add another shape and see what effort that takes and whether the original code manipulates shapes still works. There are other `tests' for object orientation (both harder and easier on the expressiveness of programming languages), but this one has the property of demonstrating a realistic problem of program organization in a small problem and has been around for about 20 years. Naturally, I'll give my version in C++ and in a programmer's rather than a software engineer's style of C++. The comments are used primarily to explain C++ features to a non-C++ programmer and only secondarily to document the program. If someone feel the urge they can complete the example (it is for example missing suitable constructors) and elaborate it suitably with comments and organizational details: // first we define the Shape type as an abstract class: class Shape { // representation details common to all shapes: point center; color col; // ... public: // user interface: point where() { return center; } void move(point to) { center = to; draw(); } virtual void draw() = 0; virtual void rotate(int angle) = 0; // ... }; // `virtual' means that dynamic binding will be used in a call of a virtual // function, `= 0' means that any class derived from this class must provide // an implementation of the function. // To define a particular shape, we must say that it is a shape // and specify its particular properties (including the virtual functions). class Circle : public Shape { int radius; public: void draw(); void rotate(int) {} // yes, the null function // ... }; void Circle::draw() { // ... } // we can now write a piece of ``user core'' manipulating Shapes: void rotate_all(Vector<Shape*>& v, int angle) // rotate all members of array `v' `angle' degrees { for (int i = 0, i<v.size(); i++) v[i].rotate(angle); } // I used a Vector template rather than a plain C array as the argument type // Vector<Shape*>& means `reference to Vector of pointers to Shapes.' // If that is considered `cheating' I can either go back to the C style // or provide the Vector definition; it is about 20 lines of C++ // adding new Shape doesn't interfere with older Shapes // or with older code manipulating Shapes: class Rectangle : public Shape { Point nw, se; public: void draw(); void rotate(int angle); // ... }; void Rectangle::draw() { // ... } void Rectangle::rotate(int angle) { // ... } Please note that I'm not claiming that all programs require the mechanisms used here (many do and many don't) nor that C++ is the only language that can express this example easily (it is not, I deliberately chose one of the oldest and most easily expressed examples of object-oriented programming techniques). Reeasonable questions in this context is to which extent abbreviations such as + for `plus' & for `reference to' and { for `start scope' affect readability and more generally how the issue of terseness vs verboseness affects readability for novices and experts. Another interesting issue is once a program has been expressed in a language what guarantees does the language provide? This relates to the issues of direct support vs programming techniques and to the thorny issue of run-time versus compile-time checking.
jls@rutabaga.Rational.COM (Jim Showalter) (04/23/91)
>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. I take it then you've never read the C book written by one of the language's inventors? You know, the one with all the one and two character identifier names? And I take it that you've never seen 'i' used as the control variable on a "for" loop? I mean, really, I've seen ZILLIONS of one or two character identifier names. That the code you've read and the code I've read have been disjoint sets seems to defy the laws of probability. Are you sure you don't want to retract your above claim? "Never" is a rather strong statement. >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. Maybe you don't, but it's a popular enough passtime that they have PUZZLE columns in the trade press. >] 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. Look, put up or shut up. I've gotten all sorts of flak about how silly/unfair it is to compare the readability of one language over another, but I haven't yet gotten an example from anybody of what they consider a readable version, in C, of my Ada example. It would seem to me that, if this is so easy to do, SOMEONE on the worldwide net would have been able to do it by now. I mean, all I'm asking people to do is use that wonderful readable C language to write a dinky little algorithm as readably as I did in Ada. Perhaps you'd like to go first? >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: [several screenfulls of heavily commented C code deleted to save bandwidth and modem agony] So, this is it? Allow me to make a few comments on your example: 1) It takes up considerably more room than my Ada example. Very odd, seeing as how a common criticism people make of Ada is that it is verbose. 2) Much of the space taken up by your example is comments. You'll note that in my example there aren't very many comments. This is deliberate: I think Ada is readable enough to be largely self-documenting. You apparently believe no such thing about C, reinforcing the very point you are struggling to disprove. 3) Much of the rest of the space taken up by your example is occupied by definitions of stuff like Boolean, which is, of course, predefined in Ada. Odd that you would find it necessary to invent Boolean yourself, since one of the criticisms people make of Ada is that it has too many "unecessary" features. Apparently Boolean is not one of them. 4) While it is laudable that you invented an assertion mechanism, this is not necessary in my example because I can use Ada's exception mechanism. Apparently exceptions are ALSO not an "unecessary" feature. 5) You have this stuff about PUBLIC and PRIVATE functions. While I find this confusing enough that I'm not sure I understand it, I BELIEVE what you are doing is inventing some sort of crude form of visibility control. As with Boolean and exceptions, this is predefined in Ada. Yet another "unecessary" feature? 6) You check if the day < 1. I don't have to do this, since the language understands constraint violations inherently. AGAIN, I suppose, an "unecessary" feature? Now, for your comments: >] 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) Uh, except that if your Month is January, it should BLOW UP, since there is no legal value for January - 1. You'll probably get away with this in C, but it's nothing to brag about. I believe the trade name for this is a "bug". You might want to write a test driver that does more than test a single middle-of-the-road-condition as is the case with your current test driver: I suggest checking boundary conditions such as the first day of the first month, etc. I did this with mine and it works just fine. With yours if C had anything approaching real constraint checking you'd fall over dead. > Also, Jim does not clearly separate functions: [package body code deleted] > 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. That's because that's all that is needed. The keywords are completely deterministic: when you see the word "function", you know you're starting the next function. Simple, non-wordy, accurate. > 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: How about if I don't? The use of comment blocks serves only to redundantly paraphrase what is already there, in the typical case. Consider your example: #>-- --------------------------------------------------------------------------- #>-- #>-- 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; #>] You have added no value and several lines with your proposed comment block. That the function returns True or False is obvious from the return value (that's what it's THERE for). That the function determines whether or not the given year is a leap year is obvious from the NAME of the damned thing (that's why I chose the name I did). And that the function is a function and is called Is_Leap_Year is obvious too. Indeed, there is nothing in your comment block that could not be programmatically derived from the existing Ada construct, which makes it COMPLETELY trivial (at least if there were some additional comments about the definition of a leap year, etc, there might be SOME value added--but the only non-derivable comment is the one I had already put there). The only time you should add comments is when the information they contain is not already obvious. This is in keeping with the principal of Minimal Noise. Of course, this is less an Ada/C issue than a commenting issue. >(If I were an Ada programmer > I would probably embelish the following comment blocks) No, you wouldn't--because most Ada style guides discourage such things. > 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. Again, this is superfluous. Ada, by design, has a "comb-like" structure: you can tell immediately from lexical level which construct you're dealing with and, because if/then/elseif/else/end-if constructs do NOT suffer from the dangling else problem of C, the matching up of an end with a begin is strictly deterministic. Besides which, if you add these comments to ends of ifs and then change the conditional on the if, the comment needs to be changed in concert. This is in violation of the principle of Single Point of Maintenance (by the way, your comment blocks are a similar violation of this principle). Or have you never noticed that comments and their associated code tend to wind up bearing no resemblance to each other? > 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: A bigot is one who is prejudiced. Prejudice is the formation of opinions prior to or in absence of the facts. I worked in C for years and years: my opinions towards it were formed AFTER I had the facts in hand. Thus I am not prejudiced, merely experienced. And thus I am not a bigot. >Actually, I think Ada has quite a few nice features that C lacks. >Personally though Ada is much too wordy for me. Which is of course why your example is three times longer than mine. >On the *average* Joe Blow's Ada code is *probably* more readable than Jim >Blow's C code. No argument. -- * The opinions expressed herein are my own, except in the realm of software * * engineering, in which case I borrowed them from incredibly smart people. * * * * Rational: cutting-edge software engineering technology and services. *
jls@rutabaga.Rational.COM (Jim Showalter) (04/23/91)
%I think that it is interesting to note that the example used in this %debate about the readability of Ada/C/C++ is in the domain where the %solutions are essentially equivalent. Do you honestly regard the C solutions as being as readable as the Ada solution? >I think that there is a significant issue in the readability of code >where a language supports or fail to support a programming paradigm. You mean like multi-thread execution not being supported by C++? ;-) %So, how about comparing the languages on the example originally used %to introduce the technique we now commonly refer to as object-oriented %programming in the Simula textbook SIMULA BEGIN? The problem is to %write a little program that define a set of shapes and manipulates %them (I'll rotate a list of shapes). Then we add another shape and %see what effort that takes and whether the original code manipulates %shapes still works. There are other `tests' for object orientation %(both harder and easier on the expressiveness of programming languages), %but this one has the property of demonstrating a realistic problem %of program organization in a small problem and has been around for %about 20 years. I've been wanting to ask someone who purports to know the answer this question for over a year. Consider the shapes example. Suppose that the given example goes out in binary form, and arrives at my site, and I want to add a new shape. Suppose that the initial shapes were limited to triangles and squares. Suppose that I now add a Circle, which has a new method defined for it that does NOT apply to triangles or squares and which was never previously defined in the base class for shapes--radius. This method returns the radius as some floating point number from 0 to whatever. Now, I want to take a heterogeneous list of shapes, including triangles, circles, and squares, and I want to iterate over the list and print out all of the radii. How do I do this? I can't get elements out of the list and call the Radius method on all of them, because not all of them HAVE such a method defined. I can't add the new method to the base class (with a null implementation as the default for those shapes for which it is a meaningless operation) because the base class is in binary. I can't ask the shapes to tell me their Kind because there is no such operation defined on them in C++. It is my claim that solving this problem in C++ results in a solution that is every bit as messy as simply using a discriminated record and an enumeration type in Ada--and Ada doesn't HAVE inheritance. -- * The opinions expressed herein are my own, except in the realm of software * * engineering, in which case I borrowed them from incredibly smart people. * * * * Rational: cutting-edge software engineering technology and services. *
ken@sugra.uucp (Kenneth Ng) (04/29/91)
In article <jls.672366979@rutabaga>, jls@rutabaga.Rational.COM (Jim Showalter) writes:
: I take it then you've never read the C book written by one of the
: language's inventors? You know, the one with all the one and two
: character identifier names? And I take it that you've never seen
: 'i' used as the control variable on a "for" loop? I mean, really,
: I've seen ZILLIONS of one or two character identifier names. That
: the code you've read and the code I've read have been disjoint sets
: seems to defy the laws of probability. Are you sure you don't want
: to retract your above claim? "Never" is a rather strong statement.
Two character variable names are not the only problem, having MEANINGFUL
variable names is another problem. Granted I come from a university
environment where some asses actually take pride in writing routines
that no one else can read. For example, I have seen old girlfriend
names used for variables. I have seen cities in denmark used for
variable names. I would say the BIGGEST single abuse of variable
names (besides the sequence 'i', 'ii', 'iii' and 'iiii' for which
I royally flamed the author) is the variable sequence 'flag', 'flag2'
up to 'flagn'. YES I KNOW ITS A FLAG, but it gives no indication of
WHAT the flag is used for!
Now to get back to comp.lang.misc, these abuses can be done in any language.
While Ada seems to be a bit wordy, the emphasis does seem to be more on
readable code rather than C's effort at going to obscure code. Granted
I tend to go too far the other way, but I have NEVER had someone ask me
"What does routine 'get_functions_using_prototyping' do?" (this is a
real function name used in a rather large REXX program).
--
Kenneth Ng
Please reply to ken@hertz.njit.edu until this machine properly recieves mail.
"No problem, here's how you build it" -- R. Barclay, ST: TNG