[comp.lang.misc] Readability of Ada

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